feat(chain): settlement program, node identity, vanity wallets, devnet scripts#171
feat(chain): settlement program, node identity, vanity wallets, devnet scripts#171RustMunkey merged 7 commits intomainfrom
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a Solana settlement subsystem (new program ID, PDAs, accounts, events) with Anchor instructions for config, node registration, vault init, earnings recording, and settlement; introduces TypeScript tooling (Helius webhook, token/realm setup, uploads, e2e), env/config changes, docs copy updates, and test/asset additions. Changes
Sequence DiagramsequenceDiagram
participant Authority as Authority
participant Program as SettlementProgram
participant Config as SettlementConfig (PDA)
participant Pool as SettlementPool (PDA)
participant Vault as NodeVault (Token Account)
participant Recipients as Recipients (Token Accounts)
Authority->>Program: initialize_config(treasury, developer, validators)
Program->>Config: create & store trusted recipient keys
Config-->>Program: created
Authority->>Program: register_node(node_id)
Program->>Pool: create NodeIdentity PDA
Pool-->>Program: registered
Authority->>Program: init_node_vault(node_id)
Program->>Vault: init USDC token account owned by Pool PDA
Vault-->>Program: initialized
Authority->>Program: add_earnings(node_id, run_id, amounts)
Program->>Vault: CPI transfer total USDC from authority -> Vault
Program->>Pool: update pending earnings
Program-->>Authority: emit EarningsRecorded
Authority->>Program: settle_earnings(node_id)
Program->>Config: verify recipients' owner keys
Program->>Vault: CPI transfers Vault -> Recipients (operator/developer/treasury/validators)
Program->>Pool: zero pending payouts
Recipients-->>Program: balances updated
Estimated code review effort🎯 4 (Complex) | ⏱️ ~70 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
content/docs/platform/economics.mdx (1)
44-55:⚠️ Potential issue | 🟡 MinorTerminology rename is incomplete within this page.
This section uses “Node Runners”, but the same page still uses “Node operators/operators” at Line 85, Line 87, and Line 118.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@content/docs/platform/economics.mdx` around lines 44 - 55, The page mixes the terms "Node Runners" and "Node operators/operators"; standardize terminology by replacing every instance of "Node operators" and "operators" with "Node Runners" (matching capitalization) so the table header, headings, and all inline references use the single agreed term; update any plural/singular forms and related possessives to "Node Runner(s)" and scan nearby JSX/text (e.g., the table row labeled "Node Runners" and the inline uses currently reading "Node operators" and "operators") to ensure consistency throughout the document.programs/settlement/src/instructions/settle_earnings.rs (1)
23-71:⚠️ Potential issue | 🔴 CriticalAuthenticate the sweep and its payout accounts.
authorityis only required to sign, anddeveloper_usdc,treasury_usdc, andvalidators_usdcare only mint-checked. That means any caller can sweep a funded vault and route those three payouts to their own token accounts. The operator share is protected bypool.operator; the other 30% is not.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@programs/settlement/src/instructions/settle_earnings.rs` around lines 23 - 71, The SettleEarnings context allows anyone with a signer to sweep funds because developer_usdc, treasury_usdc, and validators_usdc are only mint-checked; fix by authenticating the sweep authority and validating payout account owners: add or require a specific settlement authority account (e.g., a new account like settlement_authority: Signer or AccountInfo) and assert authority.key == settlement_authority.key (or compare to a stored pool.settlement_authority), and enforce owner constraints for the payout accounts by checking developer_usdc.owner == pool.developer (or the expected developer PDA), treasury_usdc.owner == expected_treasury_owner, and validators_usdc.owner == expected_validators_owner (or add those expected owner fields to SettlementPool and reference them); implement these checks either as Anchor account constraints on SettleEarnings or explicit runtime checks in the handler that reference SettleEarnings::pool, authority, developer_usdc, treasury_usdc, and validators_usdc.
🧹 Nitpick comments (3)
.releaserc.json (1)
18-18: Consider aligning breaking-change policy with Semantic Versioning.Line 18 maps breaking changes to
minorreleases, which contradicts semantic-release defaults and standard SemVer practice requiringmajorfor breaking changes. This can hide compatibility breaks from consumers. Either document this as intentional policy (e.g., for pre-1.0 development) or align with SemVer:SemVer-aligned alternative
- { "breaking": true, "release": "minor" } + { "breaking": true, "release": "major" }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.releaserc.json at line 18, Update the .releaserc.json entry that maps breaking changes (the object with "breaking": true and "release": "minor") to follow SemVer by changing its "release" value from "minor" to "major", or alternatively add a comment/README note in the repo explaining this is an intentional deviation (e.g., pre-1.0 policy) so reviewers know it’s deliberate; locate the object matching "breaking": true to apply the change.scripts/discord-scaffold.mjs (1)
124-125: Rename the map key to match updated role semantics.
roles.nodeOperatornow stores"Node Runner", which is misleading for future maintenance.Small naming cleanup
- roles.nodeOperator = await upsertRole(existing, { + roles.nodeRunner = await upsertRole(existing, { name: "Node Runner",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/discord-scaffold.mjs` around lines 124 - 125, The map key roles.nodeOperator is misleading because upsertRole(existing, { name: "Node Runner", ... }) actually stores the "Node Runner" role; rename the key to roles.nodeRunner (and update every reference to roles.nodeOperator) so the variable name matches the role semantics, and update any usages (reads/writes) that reference roles.nodeOperator to use roles.nodeRunner; ensure the upsertRole(...) call and any role checks or permissions logic (e.g., role comparisons) are updated accordingly.packages/marketplace/src/marketplace.test.ts (1)
66-72: Add an explicit treasury assertion in rounding test.Line 67 documents
treasury=17, but Line 72 checks it only implicitly through sum. A direct assertion would make regressions easier to pinpoint.Diff
it("treasury absorbs rounding (not validator)", () => { // 99 cents: node=69, dev=9, validator=4, treasury=remainder=17 const r = calcExecutionRevenue(99); expect(r.nodeCents).toBe(69); expect(r.developerCents).toBe(9); expect(r.validatorCents).toBe(4); + expect(r.treasuryCents).toBe(17); expect(r.nodeCents + r.developerCents + r.treasuryCents + r.validatorCents).toBe(99); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/marketplace/src/marketplace.test.ts` around lines 66 - 72, The test for rounding in the marketplace suite documents treasury=17 but only asserts the total sum; update the test that calls calcExecutionRevenue(99) in marketplace.test.ts to add a direct expectation for r.treasuryCents toBe(17) so the treasury rounding is checked explicitly alongside r.nodeCents, r.developerCents, and r.validatorCents.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.releaserc.json:
- Line 17: The semantic-release rule { "type": "release", "release": "major" }
in .releaserc.json is unreachable because commitlint.config.js's type-enum does
not include "release"; either remove that rule from .releaserc.json (delete the
{ "type": "release", "release": "major" } entry) or make the configs consistent
by adding "release" to the type-enum array in commitlint.config.js so commits
with type "release" pass commitlint and reach semantic-release.
In `@content/docs/platform/economics.mdx`:
- Line 152: Update the documented fee distribution percentages to match the
settlement logic in anchor_receipt.rs: change the "Compute Rewards (Node
Runners)" entry (currently "40") and the corresponding line at ~160 from the
40/30/20/10 split to the actual 65/20/10/5 split (node runners 65%, developer
20%, treasury 10%, validators 5%); edit the two entries in
content/docs/platform/economics.mdx so they reflect 65/20/10/5 before
publishing.
In `@programs/settlement/src/instructions/add_earnings.rs`:
- Around line 16-28: The AddEarnings instruction currently only records run_id
in the event, allowing duplicate processing; fix by persisting a PDA receipt
keyed by run_id and rejecting duplicates before moving funds: add a PDA account
(e.g., "earnings_receipt" + run_id) to the AddEarnings accounts struct, derive
the PDA from the provided AddEarningsArgs.run_id, check if the receipt account
already exists (and return an error if so), and create/initialize the receipt
account (marking it filled) atomically before performing any USDC transfers in
the handler that processes AddEarningsArgs/run_id so retries are detected and
prevented.
- Around line 35-68: The add_earnings instruction currently accepts any signer
as authority; require the signer to be the configured settlement authority
instead. Update the account constraint on the authority account (and/or add a
runtime check in the add_earnings handler) so authority.key() must equal the
settlement authority recorded in the Pool (e.g., compare authority.key() ==
pool.settlement_authority or use #[account(address =
pool.settlement_authority)]), and return an Unauthorized error if it doesn't
match; this ensures only the Maschina multisig can emit EarningsRecorded and
mutate pool.run_count and balances.
In `@programs/settlement/src/instructions/init_node_vault.rs`:
- Around line 30-45: The vault can be initialized against any mint because
usdc_mint is caller-controlled; lock it to the canonical USDC mint by removing
caller control and enforcing the canonical mint in the account constraint:
replace or supplement the caller-provided usdc_mint with a canonical_mint
Account<'info, Mint> (or add a canonical_mint parameter) and change the vault
attribute from token::mint = usdc_mint to token::mint = canonical_mint (or if
you must keep usdc_mint, add an explicit runtime check in the init handler
asserting usdc_mint.key() == CANONICAL_USDC_PUBKEY and fail early); update
references to usdc_mint / vault and use VAULT_SEED and args.node_id as before so
the PDA remains deterministic.
In `@scripts/discord-scaffold.mjs`:
- Line 125: Renaming "Node Runner" breaks idempotence because existing guilds
may still have legacy roles/categories named "Node Operator" or "NODE
OPERATORS"; before the create-or-skip step for the new name (the block where
name: "Node Runner" is used and any createOrSkipRole/createOrSkipCategory helper
is invoked), add a migration pass that looks up roles and categories by the
legacy names ("Node Operator", "NODE OPERATORS"), if found rename them to "Node
Runner" (and update any stored IDs/mappings) instead of creating new ones; apply
the same legacy-rename logic to the other identical name-change occurrences in
the file so the script preserves existing objects rather than duplicating them.
In `@scripts/setup-devnet.ts`:
- Around line 11-13: The header comment in scripts/setup-devnet.ts incorrectly
claims the script "Verifies the settlement program is deployed at
SETTLEMENT_PROGRAM_ID" while no RPC/account check exists; update the header to
accurately reflect behavior or add an actual on-chain check. Either (A) change
the comment text to remove "Verifies the settlement program is deployed at
SETTLEMENT_PROGRAM_ID" (e.g., state that the script assumes
SETTLEMENT_PROGRAM_ID is set) or (B) implement a runtime verification that
queries the RPC for the account/program at SETTLEMENT_PROGRAM_ID (using your
existing RPC client code) and fails/logs if not found, referencing the
identifier SETTLEMENT_PROGRAM_ID so reviewers can locate the relevant code path.
- Line 41: The webhookUrl is built from apiBaseUrl and can produce a
double-slash when API_BASE_URL ends with a slash; update the code that computes
webhookUrl (the apiBaseUrl value derived from the API_BASE_URL env and the
webhookUrl constant) to normalize apiBaseUrl first by trimming any trailing
slash (or otherwise ensuring exactly one separator) before concatenating
"/webhooks/helius", so webhookUrl is always a single-correct URL without
duplicate slashes.
In `@scripts/setup-mach-token.ts`:
- Around line 66-71: Update loadKeypair to prefer SOLANA_WALLET_PATH, fall back
to ANCHOR_WALLET, then default; expand a leading tilde before reading the file
so paths like ~/... work on Node (use os.homedir() replacement), and then read
and parse the JSON as before; reference the loadKeypair function to locate where
to change the environment variable checks and path handling.
- Around line 52-64: Add runtime validation for the CLUSTER value and make
explorer/.env hints reflect the actual cluster: validate the environment
variable assigned to CLUSTER (the variable named CLUSTER) at startup and throw a
clear error if it is not exactly "devnet" or "mainnet-beta", so the type
assertion is not relied on; keep getRpcUrl() as-is but assume CLUSTER is
validated beforehand; update the explorer link and the .env hint strings (the
code that constructs the explorer URL and the printed .env example) to use
CLUSTER dynamically (choose the appropriate explorer host for "mainnet-beta" vs
"devnet") so messages are consistent with the validated cluster.
In `@services/daemon/src/orchestrator/analyze.rs`:
- Around line 384-387: The earnings split currently hardcodes 70/10/15/5 and the
comment is wrong; add a developer attribution flag to JobToRun (e.g.,
developer_id: Option<Uuid> or is_first_party: bool), populate it from the
agent/run metadata where JobToRun is constructed, pass that field through to
record_node_earnings, and change the split logic in analyze.rs: if first-party
use 70/25/0/5 (node/treasury/developer/validator) else use 70/15/10/5
(node/treasury/developer/validator); also update the function comment to match
the implemented conditional behavior.
---
Outside diff comments:
In `@content/docs/platform/economics.mdx`:
- Around line 44-55: The page mixes the terms "Node Runners" and "Node
operators/operators"; standardize terminology by replacing every instance of
"Node operators" and "operators" with "Node Runners" (matching capitalization)
so the table header, headings, and all inline references use the single agreed
term; update any plural/singular forms and related possessives to "Node
Runner(s)" and scan nearby JSX/text (e.g., the table row labeled "Node Runners"
and the inline uses currently reading "Node operators" and "operators") to
ensure consistency throughout the document.
In `@programs/settlement/src/instructions/settle_earnings.rs`:
- Around line 23-71: The SettleEarnings context allows anyone with a signer to
sweep funds because developer_usdc, treasury_usdc, and validators_usdc are only
mint-checked; fix by authenticating the sweep authority and validating payout
account owners: add or require a specific settlement authority account (e.g., a
new account like settlement_authority: Signer or AccountInfo) and assert
authority.key == settlement_authority.key (or compare to a stored
pool.settlement_authority), and enforce owner constraints for the payout
accounts by checking developer_usdc.owner == pool.developer (or the expected
developer PDA), treasury_usdc.owner == expected_treasury_owner, and
validators_usdc.owner == expected_validators_owner (or add those expected owner
fields to SettlementPool and reference them); implement these checks either as
Anchor account constraints on SettleEarnings or explicit runtime checks in the
handler that reference SettleEarnings::pool, authority, developer_usdc,
treasury_usdc, and validators_usdc.
---
Nitpick comments:
In @.releaserc.json:
- Line 18: Update the .releaserc.json entry that maps breaking changes (the
object with "breaking": true and "release": "minor") to follow SemVer by
changing its "release" value from "minor" to "major", or alternatively add a
comment/README note in the repo explaining this is an intentional deviation
(e.g., pre-1.0 policy) so reviewers know it’s deliberate; locate the object
matching "breaking": true to apply the change.
In `@packages/marketplace/src/marketplace.test.ts`:
- Around line 66-72: The test for rounding in the marketplace suite documents
treasury=17 but only asserts the total sum; update the test that calls
calcExecutionRevenue(99) in marketplace.test.ts to add a direct expectation for
r.treasuryCents toBe(17) so the treasury rounding is checked explicitly
alongside r.nodeCents, r.developerCents, and r.validatorCents.
In `@scripts/discord-scaffold.mjs`:
- Around line 124-125: The map key roles.nodeOperator is misleading because
upsertRole(existing, { name: "Node Runner", ... }) actually stores the "Node
Runner" role; rename the key to roles.nodeRunner (and update every reference to
roles.nodeOperator) so the variable name matches the role semantics, and update
any usages (reads/writes) that reference roles.nodeOperator to use
roles.nodeRunner; ensure the upsertRole(...) call and any role checks or
permissions logic (e.g., role comparisons) are updated accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 0118aff8-3921-44e3-b826-c82d87137d3c
📒 Files selected for processing (24)
.env.example.releaserc.jsonascii-logo.txtcontent/docs/platform/economics.mdxcontent/docs/platform/nodes.mdxcontent/docs/platform/overview.mdxcontent/docs/platform/roadmap.mdxpackages/chain/src/settlement.tspackages/db/src/schema/pg/nodes.tspackages/marketplace/src/index.tspackages/marketplace/src/marketplace.test.tsprograms/Anchor.tomlprograms/settlement/Cargo.tomlprograms/settlement/src/errors.rsprograms/settlement/src/instructions/add_earnings.rsprograms/settlement/src/instructions/init_node_vault.rsprograms/settlement/src/instructions/mod.rsprograms/settlement/src/instructions/settle_earnings.rsprograms/settlement/src/lib.rsprograms/settlement/src/state.rsscripts/discord-scaffold.mjsscripts/setup-devnet.tsscripts/setup-mach-token.tsservices/daemon/src/orchestrator/analyze.rs
| { "type": "chore", "release": false }, | ||
| { "type": "ci", "release": false }, | ||
| { "type": "test", "release": false }, | ||
| { "type": "release", "release": "major" }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Checking semantic-release rule:"
rg -n '"type"\s*:\s*"release"' .releaserc.json
echo
echo "Checking commitlint allowed types:"
rg -n -A30 -B2 '"type-enum"' commitlint.config.jsRepository: RustMunkey/maschina
Length of output: 892
Remove unreachable release commit type rule from semantic-release configuration.
Line 17 defines a major bump for type: "release", but commitlint.config.js (lines 7-24) does not include "release" in its type-enum, so this rule is unreachable—commits with this type will be rejected by commitlint before reaching semantic-release.
Either remove this rule from .releaserc.json or add "release" to the type-enum in commitlint.config.js to make the configuration consistent.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.releaserc.json at line 17, The semantic-release rule { "type": "release",
"release": "major" } in .releaserc.json is unreachable because
commitlint.config.js's type-enum does not include "release"; either remove that
rule from .releaserc.json (delete the { "type": "release", "release": "major" }
entry) or make the configs consistent by adding "release" to the type-enum array
in commitlint.config.js so commits with type "release" pass commitlint and reach
semantic-release.
| ```mermaid | ||
| pie title Network Fee Distribution | ||
| "Compute Rewards (Node Operators)" : 40 | ||
| "Compute Rewards (Node Runners)" : 40 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Compare settlement split references across chain/program/docs.
rg -n -C2 '70/15/10/5|40/30/20/10|Compute Rewards|Protocol Treasury|validator|node runners' \
packages/chain/src/settlement.ts \
content/docs/platform/economics.mdx \
programs/settlement/srcRepository: RustMunkey/maschina
Length of output: 12518
Update fee distribution percentages to match current settlement implementation.
The documentation claims a 40/30/20/10 split, but the actual anchor_receipt.rs settlement code implements 65/20/10/5 (node runners 65%, developer 20%, treasury 10%, validators 5%). Update lines 152 and 160 to reflect the correct percentages before publication.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@content/docs/platform/economics.mdx` at line 152, Update the documented fee
distribution percentages to match the settlement logic in anchor_receipt.rs:
change the "Compute Rewards (Node Runners)" entry (currently "40") and the
corresponding line at ~160 from the 40/30/20/10 split to the actual 65/20/10/5
split (node runners 65%, developer 20%, treasury 10%, validators 5%); edit the
two entries in content/docs/platform/economics.mdx so they reflect 65/20/10/5
before publishing.
| pub struct AddEarningsArgs { | ||
| pub node_id: [u8; 16], | ||
| /// Run ID — stored in the event for off-chain reconciliation. | ||
| pub run_id: [u8; 16], | ||
| /// Node runner's share in USDC lamports (70%). | ||
| pub node_amount: u64, | ||
| /// Developer royalty in USDC lamports (10%, 0 for first-party agents). | ||
| pub developer_amount: u64, | ||
| /// Treasury share in USDC lamports (15%, or 25% when developer_amount == 0). | ||
| pub treasury_amount: u64, | ||
| /// Validators share in USDC lamports (5%). | ||
| pub validator_amount: u64, | ||
| } |
There was a problem hiding this comment.
Make run_id an on-chain dedupe key.
run_id is only written to the event. If the settlement worker retries or replays the same run, this instruction transfers USDC again and increments the pool again. For real-money settlement, persist a PDA/receipt keyed by run_id and reject duplicates before moving funds.
Also applies to: 73-124
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@programs/settlement/src/instructions/add_earnings.rs` around lines 16 - 28,
The AddEarnings instruction currently only records run_id in the event, allowing
duplicate processing; fix by persisting a PDA receipt keyed by run_id and
rejecting duplicates before moving funds: add a PDA account (e.g.,
"earnings_receipt" + run_id) to the AddEarnings accounts struct, derive the PDA
from the provided AddEarningsArgs.run_id, check if the receipt account already
exists (and return an error if so), and create/initialize the receipt account
(marking it filled) atomically before performing any USDC transfers in the
handler that processes AddEarningsArgs/run_id so retries are detected and
prevented.
| /// Settlement authority (Maschina multisig) — signs and funds the transfer. | ||
| #[account(mut)] | ||
| pub authority: Signer<'info>, | ||
|
|
||
| /// USDC mint. | ||
| pub usdc_mint: Account<'info, Mint>, | ||
|
|
||
| /// Settlement authority's USDC token account — source of funds. | ||
| #[account( | ||
| mut, | ||
| token::mint = usdc_mint, | ||
| token::authority = authority, | ||
| )] | ||
| pub authority_usdc: Account<'info, TokenAccount>, | ||
|
|
||
| /// Pool PDA — accumulates pending earnings. | ||
| #[account( | ||
| mut, | ||
| seeds = [b"pool", args.node_id.as_ref()], | ||
| bump = pool.bump, | ||
| )] | ||
| pub pool: Account<'info, SettlementPool>, | ||
|
|
||
| /// Per-node USDC vault — receives the total earnings. | ||
| #[account( | ||
| mut, | ||
| seeds = [VAULT_SEED, args.node_id.as_ref()], | ||
| bump, | ||
| token::mint = usdc_mint, | ||
| token::authority = pool, | ||
| )] | ||
| pub vault: Account<'info, TokenAccount>, | ||
|
|
||
| pub token_program: Program<'info, Token>, |
There was a problem hiding this comment.
add_earnings is missing its authority check.
Right now this only requires “some signer with some token account”. Any user can emit EarningsRecorded, mutate pending balances, and bump run_count, even though the split is supposed to be computed by the Maschina settlement authority.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@programs/settlement/src/instructions/add_earnings.rs` around lines 35 - 68,
The add_earnings instruction currently accepts any signer as authority; require
the signer to be the configured settlement authority instead. Update the account
constraint on the authority account (and/or add a runtime check in the
add_earnings handler) so authority.key() must equal the settlement authority
recorded in the Pool (e.g., compare authority.key() == pool.settlement_authority
or use #[account(address = pool.settlement_authority)]), and return an
Unauthorized error if it doesn't match; this ensures only the Maschina multisig
can emit EarningsRecorded and mutate pool.run_count and balances.
| /// USDC mint. | ||
| /// On devnet use: 4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU | ||
| /// On mainnet use: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v | ||
| pub usdc_mint: Account<'info, Mint>, | ||
|
|
||
| /// Per-node USDC vault. Authority = pool PDA so only the program can move funds. | ||
| /// Seeded with [VAULT_SEED, node_id] to keep it deterministic and auditable. | ||
| #[account( | ||
| init, | ||
| payer = payer, | ||
| seeds = [VAULT_SEED, args.node_id.as_ref()], | ||
| bump, | ||
| token::mint = usdc_mint, | ||
| token::authority = pool, | ||
| )] | ||
| pub vault: Account<'info, TokenAccount>, |
There was a problem hiding this comment.
Lock this vault to canonical USDC before first init.
usdc_mint is fully caller-controlled here. Because this PDA is initialized only once, the first caller can create the vault against any mint; every later add_earnings / settle_earnings call is then forced to use that mint or fail, which effectively bricks USDC settlement for the node.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@programs/settlement/src/instructions/init_node_vault.rs` around lines 30 -
45, The vault can be initialized against any mint because usdc_mint is
caller-controlled; lock it to the canonical USDC mint by removing caller control
and enforcing the canonical mint in the account constraint: replace or
supplement the caller-provided usdc_mint with a canonical_mint Account<'info,
Mint> (or add a canonical_mint parameter) and change the vault attribute from
token::mint = usdc_mint to token::mint = canonical_mint (or if you must keep
usdc_mint, add an explicit runtime check in the init handler asserting
usdc_mint.key() == CANONICAL_USDC_PUBKEY and fail early); update references to
usdc_mint / vault and use VAULT_SEED and args.node_id as before so the PDA
remains deterministic.
| * 1. Verifies the settlement program is deployed at SETTLEMENT_PROGRAM_ID | ||
| * 2. Registers a Helius webhook for settlement events → API_BASE_URL/webhooks/helius | ||
| * 3. Prints the webhook ID — save to .env as HELIUS_WEBHOOK_ID |
There was a problem hiding this comment.
Header comment currently overstates behavior.
Line 11 says the script verifies deployment at SETTLEMENT_PROGRAM_ID, but no RPC/account check is performed. Either add that check or reword the comment to avoid false confidence.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/setup-devnet.ts` around lines 11 - 13, The header comment in
scripts/setup-devnet.ts incorrectly claims the script "Verifies the settlement
program is deployed at SETTLEMENT_PROGRAM_ID" while no RPC/account check exists;
update the header to accurately reflect behavior or add an actual on-chain
check. Either (A) change the comment text to remove "Verifies the settlement
program is deployed at SETTLEMENT_PROGRAM_ID" (e.g., state that the script
assumes SETTLEMENT_PROGRAM_ID is set) or (B) implement a runtime verification
that queries the RPC for the account/program at SETTLEMENT_PROGRAM_ID (using
your existing RPC client code) and fails/logs if not found, referencing the
identifier SETTLEMENT_PROGRAM_ID so reviewers can locate the relevant code path.
| console.log(`API base URL: ${apiBaseUrl}`); | ||
|
|
||
| const helius = getHeliusClient(); | ||
| const webhookUrl = `${apiBaseUrl}/webhooks/helius`; |
There was a problem hiding this comment.
Normalize API_BASE_URL before building webhookUrl.
Line 41 can create //webhooks/helius when API_BASE_URL ends with /. That may break idempotency matching in webhook registration and create duplicate webhooks.
Diff
- const apiBaseUrl = process.env.API_BASE_URL;
+ const apiBaseUrl = process.env.API_BASE_URL?.replace(/\/+$/, "");
if (!apiBaseUrl) {
@@
- const webhookUrl = `${apiBaseUrl}/webhooks/helius`;
+ const webhookUrl = new URL("/webhooks/helius", `${apiBaseUrl}/`).toString();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/setup-devnet.ts` at line 41, The webhookUrl is built from apiBaseUrl
and can produce a double-slash when API_BASE_URL ends with a slash; update the
code that computes webhookUrl (the apiBaseUrl value derived from the
API_BASE_URL env and the webhookUrl constant) to normalize apiBaseUrl first by
trimming any trailing slash (or otherwise ensuring exactly one separator) before
concatenating "/webhooks/helius", so webhookUrl is always a single-correct URL
without duplicate slashes.
scripts/setup-mach-token.ts
Outdated
| const CLUSTER = (process.env.SOLANA_CLUSTER ?? "devnet") as "devnet" | "mainnet-beta"; | ||
|
|
||
| function getRpcUrl(): string { | ||
| const apiKey = process.env.HELIUS_API_KEY; | ||
| if (apiKey) { | ||
| return CLUSTER === "mainnet-beta" | ||
| ? `https://mainnet.helius-rpc.com/?api-key=${apiKey}` | ||
| : `https://devnet.helius-rpc.com/?api-key=${apiKey}`; | ||
| } | ||
| return CLUSTER === "mainnet-beta" | ||
| ? "https://api.mainnet-beta.solana.com" | ||
| : "https://api.devnet.solana.com"; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In TypeScript, does const x = process.env.SOLANA_CLUSTER as "devnet" | "mainnet-beta" validate the runtime value, or does it only narrow the static type?
💡 Result:
In TypeScript, const x = process.env.SOLANA_CLUSTER as "devnet" | "mainnet-beta" only narrows the static type at compile time. It does not validate the runtime value of process.env.SOLANA_CLUSTER. Type assertions using as are a compile-time-only feature that tell the TypeScript compiler to treat the value as the specified type without performing any runtime checks or changes to the value. The emitted JavaScript is identical to const x = process.env.SOLANA_CLUSTER;, so if the environment variable is "testnet", undefined, or any other value not in the union, x will hold that invalid value at runtime, potentially causing errors later when using x. For runtime validation of environment variables like this, you need explicit checks, such as: const cluster = process.env.SOLANA_CLUSTER; if (!cluster || (cluster !== "devnet" && cluster !== "mainnet-beta")) { throw new Error(Invalid SOLANA_CLUSTER: ${cluster}); } const x: "devnet" | "mainnet-beta" = cluster; // Now safe to assert Or use libraries like Zod for schema validation with type inference.
Citations:
- 1: https://www.convex.dev/typescript/advanced/advanced-concepts/typescript-assert
- 2: https://www.typescriptlang.org/docs/handbook/advanced-types.html
- 3: https://mimo.org/glossary/typescript/type-assertion
- 4: https://stackoverflow.com/questions/55781559/what-does-the-as-keyword-do
🏁 Script executed:
# First, check if the file exists and get its line count
if [ -f "scripts/setup-mach-token.ts" ]; then
wc -l scripts/setup-mach-token.ts
echo "---"
# Read the specific lines mentioned in the review
echo "Lines 52-64:"
sed -n '52,64p' scripts/setup-mach-token.ts
echo "---"
echo "Lines 164-167:"
sed -n '164,167p' scripts/setup-mach-token.ts
fiRepository: RustMunkey/maschina
Length of output: 818
Add runtime validation for SOLANA_CLUSTER environment variable.
The type assertion on line 52 only narrows the static type; it does not validate the runtime value. Any invalid string (e.g., "testnet", "staging") will silently fall through to devnet URLs in getRpcUrl(). Additionally, the explorer link and .env hints at lines 164–167 are hardcoded to devnet, which is inconsistent when CLUSTER is "mainnet-beta" and creates a confusing user experience.
Implement explicit runtime validation that throws an error for invalid cluster values, and update the explorer link and .env hints to reflect the actual cluster:
Proposed fix
-const CLUSTER = (process.env.SOLANA_CLUSTER ?? "devnet") as "devnet" | "mainnet-beta";
+function parseCluster(): "devnet" | "mainnet-beta" {
+ const value = (process.env.SOLANA_CLUSTER ?? "devnet").trim();
+ if (value !== "devnet" && value !== "mainnet-beta") {
+ throw new Error(`Invalid SOLANA_CLUSTER: ${value}`);
+ }
+ return value;
+}
+
+const CLUSTER = parseCluster();
@@
- console.log(` Explorer: https://explorer.solana.com/address/${mintAddress}?cluster=devnet`);
+ const explorerSuffix = CLUSTER === "mainnet-beta" ? "" : `?cluster=${CLUSTER}`;
+ console.log(` Explorer: https://explorer.solana.com/address/${mintAddress}${explorerSuffix}`);
console.log("\nAdd to .env:");
console.log(` MACH_MINT_ADDRESS=${mintAddress}`);
- console.log(" SOLANA_CLUSTER=devnet");
+ console.log(` SOLANA_CLUSTER=${CLUSTER}`);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/setup-mach-token.ts` around lines 52 - 64, Add runtime validation for
the CLUSTER value and make explorer/.env hints reflect the actual cluster:
validate the environment variable assigned to CLUSTER (the variable named
CLUSTER) at startup and throw a clear error if it is not exactly "devnet" or
"mainnet-beta", so the type assertion is not relied on; keep getRpcUrl() as-is
but assume CLUSTER is validated beforehand; update the explorer link and the
.env hint strings (the code that constructs the explorer URL and the printed
.env example) to use CLUSTER dynamically (choose the appropriate explorer host
for "mainnet-beta" vs "devnet") so messages are consistent with the validated
cluster.
| function loadKeypair(): Keypair { | ||
| const walletPath = | ||
| process.env.ANCHOR_WALLET ?? path.join(os.homedir(), ".config", "solana", "id.json"); | ||
| const raw = fs.readFileSync(walletPath, "utf-8"); | ||
| const bytes = JSON.parse(raw) as number[]; | ||
| return Keypair.fromSecretKey(Uint8Array.from(bytes)); |
There was a problem hiding this comment.
Read SOLANA_WALLET_PATH here too.
The new .env.example advertises SOLANA_WALLET_PATH, but this script ignores it and only checks ANCHOR_WALLET. On any machine that does not use the default Solana CLI path, this will mint under the wrong authority or fail outright; Node also will not expand the ~/... example for you.
🛠️ Proposed fix
+function resolveWalletPath(): string {
+ const configured =
+ process.env.ANCHOR_WALLET ??
+ process.env.SOLANA_WALLET_PATH ??
+ path.join(os.homedir(), ".config", "solana", "id.json");
+
+ return configured.startsWith("~/")
+ ? path.join(os.homedir(), configured.slice(2))
+ : configured;
+}
+
function loadKeypair(): Keypair {
- const walletPath =
- process.env.ANCHOR_WALLET ?? path.join(os.homedir(), ".config", "solana", "id.json");
+ const walletPath = resolveWalletPath();
const raw = fs.readFileSync(walletPath, "utf-8");
const bytes = JSON.parse(raw) as number[];
return Keypair.fromSecretKey(Uint8Array.from(bytes));
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function loadKeypair(): Keypair { | |
| const walletPath = | |
| process.env.ANCHOR_WALLET ?? path.join(os.homedir(), ".config", "solana", "id.json"); | |
| const raw = fs.readFileSync(walletPath, "utf-8"); | |
| const bytes = JSON.parse(raw) as number[]; | |
| return Keypair.fromSecretKey(Uint8Array.from(bytes)); | |
| function resolveWalletPath(): string { | |
| const configured = | |
| process.env.ANCHOR_WALLET ?? | |
| process.env.SOLANA_WALLET_PATH ?? | |
| path.join(os.homedir(), ".config", "solana", "id.json"); | |
| return configured.startsWith("~/") | |
| ? path.join(os.homedir(), configured.slice(2)) | |
| : configured; | |
| } | |
| function loadKeypair(): Keypair { | |
| const walletPath = resolveWalletPath(); | |
| const raw = fs.readFileSync(walletPath, "utf-8"); | |
| const bytes = JSON.parse(raw) as number[]; | |
| return Keypair.fromSecretKey(Uint8Array.from(bytes)); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/setup-mach-token.ts` around lines 66 - 71, Update loadKeypair to
prefer SOLANA_WALLET_PATH, fall back to ANCHOR_WALLET, then default; expand a
leading tilde before reading the file so paths like ~/... work on Node (use
os.homedir() replacement), and then read and parse the JSON as before; reference
the loadKeypair function to locate where to change the environment variable
checks and path handling.
| let node_cents = (total as f64 * 0.70).floor() as i64; | ||
| let developer_cents = (total as f64 * 0.10).floor() as i64; | ||
| let treasury_cents = (total as f64 * 0.15).floor() as i64; | ||
| let validator_cents = total - node_cents - developer_cents - treasury_cents; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify whether run metadata exposes a first-party/developer signal,
# and where the earnings split is currently computed.
set -euo pipefail
echo "== Locate JobToRun definition =="
fd -i "scan_compat.rs" | xargs -r sed -n '1,260p'
echo
echo "== Search for developer/first-party attribution fields in daemon orchestrator =="
rg -n --type=rust -C3 '\b(first[_-]?party|developer|template_author|author_id|with_developer)\b' services/daemon/src
echo
echo "== Confirm all split computations in analyze path =="
rg -n --type=rust -C4 'record_node_earnings|node_cents|developer_cents|treasury_cents|validator_cents' services/daemon/src/orchestratorRepository: RustMunkey/maschina
Length of output: 5922
🏁 Script executed:
#!/bin/bash
echo "== Find QueuedRun struct definition =="
rg -n --type=rust 'struct QueuedRun' -A 20
echo
echo "== Check packages/db schema for first-party documentation =="
if [ -f packages/db/src/schema/pg/nodes.ts ]; then
sed -n '200,210p' packages/db/src/schema/pg/nodes.ts
else
fd -i nodes.ts | head -5
fi
echo
echo "== Check marketplace test for first-party test case =="
if [ -f packages/marketplace/src/marketplace.test.ts ]; then
sed -n '50,60p' packages/marketplace/src/marketplace.test.ts
else
fd -i marketplace.test.ts | head -5
fi
echo
echo "== Search for any first-party or developer_id handling in analyze.rs =="
rg -n --type=rust -B5 -A10 'first.party|developer_id|template_author' services/daemon/src/orchestrator/analyze.rsRepository: RustMunkey/maschina
Length of output: 1314
🏁 Script executed:
#!/bin/bash
echo "== Find QueuedRun with broader search =="
rg -n --type=rust 'QueuedRun' -B2 -A15 | head -80
echo
echo "== Check if developer_id exists anywhere in run context =="
rg -n --type=rust 'developer_id|is_first_party|first_party' services/daemon/src
echo
echo "== Look at full record_node_earnings signature and context =="
sed -n '361,420p' services/daemon/src/orchestrator/analyze.rsRepository: RustMunkey/maschina
Length of output: 7631
Pass developer_id signal through JobToRun and apply conditional split for first-party agents.
Line 385 and 386 unconditionally allocate 10% to developer and 15% to treasury. However, JobToRun lacks any developer attribution field, making it impossible to distinguish first-party agents. For first-party agents, the split should be 70/25/0/5 (node/treasury/developer/validator) per the schema documentation and test expectations (packages/db/src/schema/pg/nodes.ts and packages/marketplace/src/marketplace.test.ts Line 51–56), not 70/15/10/5.
Additionally, the function comment (line 362) incorrectly states "70/15/10/5" when the code implements 70/10/15/5.
To fix: (1) extend JobToRun with a developer_id or is_first_party field from the agent/run metadata, (2) thread this through to record_node_earnings, and (3) apply conditional split logic.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@services/daemon/src/orchestrator/analyze.rs` around lines 384 - 387, The
earnings split currently hardcodes 70/10/15/5 and the comment is wrong; add a
developer attribution flag to JobToRun (e.g., developer_id: Option<Uuid> or
is_first_party: bool), populate it from the agent/run metadata where JobToRun is
constructed, pass that field through to record_node_earnings, and change the
split logic in analyze.rs: if first-party use 70/25/0/5
(node/treasury/developer/validator) else use 70/15/10/5
(node/treasury/developer/validator); also update the function comment to match
the implemented conditional behavior.
97a3acb to
6ab935f
Compare
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (8)
programs/settlement/src/instructions/init_node_vault.rs (1)
29-44:⚠️ Potential issue | 🔴 CriticalCritical: Validate
usdc_mintagainst canonical USDC to prevent bricking settlement.The
usdc_mintaccount is fully caller-controlled. Since this PDA is initialized only once, a malicious or mistaken first caller can create the vault against any mint. All subsequentadd_earnings/settle_earningscalls would then fail or be forced to use that incorrect mint, effectively bricking USDC settlement for the node.Proposed fix: Add canonical USDC validation
Option 1: Add a runtime check in the handler:
+use crate::errors::SettlementError; + +// Canonical USDC mints +pub const USDC_MINT_DEVNET: &str = "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"; +pub const USDC_MINT_MAINNET: &str = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"; pub fn handler(ctx: Context<InitNodeVault>, _args: InitNodeVaultArgs) -> Result<()> { + // Validate usdc_mint is canonical USDC (devnet or mainnet) + let mint_key = ctx.accounts.usdc_mint.key().to_string(); + require!( + mint_key == USDC_MINT_DEVNET || mint_key == USDC_MINT_MAINNET, + SettlementError::InvalidUsdcMint + ); + // The account is fully initialised by the #[account(init)] constraint.Option 2: Store canonical USDC mint in
SettlementConfigand validate against it:#[derive(Accounts)] #[instruction(args: InitNodeVaultArgs)] pub struct InitNodeVault<'info> { + /// Global config — contains canonical USDC mint for this cluster. + #[account( + seeds = [SettlementConfig::SEED], + bump = config.bump, + )] + pub config: Account<'info, SettlementConfig>, + /// USDC mint — must match config.usdc_mint. - pub usdc_mint: Account<'info, Mint>, + #[account( + constraint = usdc_mint.key() == config.usdc_mint @ SettlementError::InvalidUsdcMint + )] + pub usdc_mint: Account<'info, Mint>,Option 2 is preferred as it keeps the canonical mint configurable per-cluster via
initialize_config.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@programs/settlement/src/instructions/init_node_vault.rs` around lines 29 - 44, The vault init currently accepts a caller-provided usdc_mint allowing a malicious first initializer to bind the vault to a wrong mint; add validation to ensure the mint equals the canonical USDC mint stored in your config: extend SettlementConfig to include a canonical_usdc_mint field (set via initialize_config), then in the init_node_vault handler check that the provided usdc_mint.key() matches SettlementConfig::canonical_usdc_mint and return an error if not; update any callers/tests to supply the canonical mint and keep add_earnings/settle_earnings behavior unchanged but now safe because vault/usdc_mint are validated.scripts/setup-mach-token.ts (3)
67-73:⚠️ Potential issue | 🟠 MajorRead
SOLANA_WALLET_PATHand expand tilde paths.The
.env.exampleadvertisesSOLANA_WALLET_PATH, but this script only checksANCHOR_WALLET. Paths like~/.config/solana/id.jsonwon't expand correctly in Node.js.Proposed fix
function loadKeypair(): Keypair { - const walletPath = - process.env.ANCHOR_WALLET ?? path.join(os.homedir(), ".config", "solana", "id.json"); + const configuredPath = + process.env.ANCHOR_WALLET ?? + process.env.SOLANA_WALLET_PATH ?? + path.join(os.homedir(), ".config", "solana", "id.json"); + + const walletPath = configuredPath.startsWith("~/") + ? path.join(os.homedir(), configuredPath.slice(2)) + : configuredPath; + const raw = fs.readFileSync(walletPath, "utf-8"); const bytes = JSON.parse(raw) as number[]; return Keypair.fromSecretKey(Uint8Array.from(bytes)); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/setup-mach-token.ts` around lines 67 - 73, The loadKeypair function currently only reads ANCHOR_WALLET and doesn't expand tilde paths; update loadKeypair to prefer SOLANA_WALLET_PATH (fall back to ANCHOR_WALLET then the existing default), and ensure any path starting with "~" is expanded to the user home directory (e.g., replace leading "~" with os.homedir()) before passing to fs.readFileSync/Path APIs so "~/.config/solana/id.json" resolves correctly; keep the rest of the logic (JSON parse and Keypair.fromSecretKey) unchanged.
52-65:⚠️ Potential issue | 🟡 MinorAdd runtime validation for
SOLANA_CLUSTER.The
CLUSTERvariable accepts any string at runtime. Invalid values like"testnet"or"staging"will silently fall through to devnet URLs ingetRpcUrl(). The code also supports"localnet"(line 55) which isn't documented.Proposed fix
-const CLUSTER = process.env.SOLANA_CLUSTER ?? "devnet"; +function parseCluster(): "devnet" | "mainnet-beta" | "localnet" { + const value = (process.env.SOLANA_CLUSTER ?? "devnet").trim(); + if (value !== "devnet" && value !== "mainnet-beta" && value !== "localnet") { + throw new Error(`Invalid SOLANA_CLUSTER: ${value}. Must be "devnet", "mainnet-beta", or "localnet".`); + } + return value; +} + +const CLUSTER = parseCluster();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/setup-mach-token.ts` around lines 52 - 65, The CLUSTER value (from SOLANA_CLUSTER) is not validated and can silently map unknown strings to devnet; update getRpcUrl (or the initialization of CLUSTER) to validate the environment value against an explicit allowed set (e.g., "localnet", "devnet", "mainnet-beta"), and if the value is invalid either throw an Error or log a clear message and exit so misconfigured deployments fail fast; make sure to reference CLUSTER and getRpcUrl in the message and state the supported values so callers know the valid options.
172-176:⚠️ Potential issue | 🟡 MinorMake explorer link and
.envhints dynamic based onCLUSTER.The explorer URL and
.envguidance are hardcoded to devnet. When running withCLUSTER=mainnet-beta, this creates a misleading user experience.Proposed fix
- console.log(` Explorer: https://explorer.solana.com/address/${mintAddress}?cluster=devnet`); + const explorerSuffix = CLUSTER === "mainnet-beta" ? "" : `?cluster=${CLUSTER}`; + console.log(` Explorer: https://explorer.solana.com/address/${mintAddress}${explorerSuffix}`); console.log("\nAdd to .env:"); console.log(` MACH_MINT_ADDRESS=${mintAddress}`); - console.log(" SOLANA_CLUSTER=devnet"); + console.log(` SOLANA_CLUSTER=${CLUSTER}`);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/setup-mach-token.ts` around lines 172 - 176, The printed explorer URL and .env hints are hardcoded to devnet; update the console output to use the CLUSTER value (read from process.env.CLUSTER or the existing cluster variable) and fall back to "devnet" if unset, so the Explorer link (constructed with mintAddress and the cluster query param) and the suggested SOLANA_CLUSTER value reflect the actual cluster (e.g., "devnet" or "mainnet-beta"); change the three console.log lines that reference mintAddress and the hardcoded "devnet" to interpolate the resolved cluster variable instead.scripts/setup-devnet.ts (2)
28-41:⚠️ Potential issue | 🟠 MajorNormalize
API_BASE_URLto prevent double slashes in webhook URL.When
API_BASE_URLends with/, the concatenation creates//webhooks/helius, which may break idempotency matching and create duplicate webhooks.Proposed fix
- const apiBaseUrl = process.env.API_BASE_URL; + const apiBaseUrl = process.env.API_BASE_URL?.replace(/\/+$/, ""); if (!apiBaseUrl) { console.error( "API_BASE_URL is not set. Set it to your public API URL (e.g. https://api.maschina.ai)", ); process.exit(1); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/setup-devnet.ts` around lines 28 - 41, The webhook URL construction can produce a double slash when API_BASE_URL ends with a slash; normalize apiBaseUrl before building webhookUrl by trimming any trailing slash (operate on the apiBaseUrl variable obtained from process.env.API_BASE_URL) or use a URL-join approach when composing the webhookUrl variable; update the logic where const apiBaseUrl is validated and where const webhookUrl = `${apiBaseUrl}/webhooks/helius` is defined (and keep getHeliusClient() usages unchanged) so webhookUrl always becomes "<origin>/webhooks/helius" without duplicated slashes.
10-14:⚠️ Potential issue | 🟡 MinorHeader comment overstates behavior.
Line 11 states the script "Verifies the settlement program is deployed" but no RPC/account check is performed. Either add actual verification or reword the comment.
Proposed fix (update comment)
* What it does: -* 1. Verifies the settlement program is deployed at SETTLEMENT_PROGRAM_ID -* 2. Registers a Helius webhook for settlement events → API_BASE_URL/webhooks/helius -* 3. Prints the webhook ID — save to .env as HELIUS_WEBHOOK_ID +* 1. Registers a Helius webhook for settlement events → API_BASE_URL/webhooks/helius +* 2. Prints the webhook ID — save to .env as HELIUS_WEBHOOK_ID🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/setup-devnet.ts` around lines 10 - 14, The header comment claims the script "Verifies the settlement program is deployed at SETTLEMENT_PROGRAM_ID" but no RPC/account check exists; either remove or reword that claim or implement an actual verification: locate the SETTLEMENT_PROGRAM_ID constant in scripts/setup-devnet.ts and either change the header to say the script "assumes the settlement program is deployed at SETTLEMENT_PROGRAM_ID" or add a verification step using the Solana Connection (e.g., connection.getAccountInfo or getProgramAccounts) to check for an account/executable at SETTLEMENT_PROGRAM_ID and fail/log if missing before proceeding to the Helius webhook registration and printing of HELIUS_WEBHOOK_ID.programs/settlement/src/instructions/add_earnings.rs (2)
16-28:⚠️ Potential issue | 🔴 Critical
run_idstill lacks on-chain deduplication—replay vulnerability persists.The
run_idis only emitted in the event (line 118) but not persisted. If the settlement worker retries or replays a run, this instruction:
- Transfers USDC again from the authority
- Increments
pending_*balances again- Bumps
run_countagainFor real-money settlement, persist a receipt PDA keyed by
run_idand reject duplicates before moving funds.🛡️ Sketch of dedupe approach
Add a receipt account to prevent replay:
/// Ensures this run_id hasn't been processed before. #[account( init, payer = authority, seeds = [b"earnings_receipt", args.run_id.as_ref()], bump, space = 8 + 1, // discriminator + processed flag )] pub receipt: Account<'info, EarningsReceipt>,If the receipt already exists, Anchor's
initconstraint will fail withAccountAlreadyInUse, providing automatic idempotency.Also applies to: 116-122
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@programs/settlement/src/instructions/add_earnings.rs` around lines 16 - 28, The AddEarnings instruction currently only emits run_id in an event and is vulnerable to replay because run_id is not persisted; add a receipt PDA keyed by args.run_id to enforce idempotency by rejecting duplicate runs before moving funds. Modify the AddEarnings handler to require an Account<'info', EarningsReceipt> (initialized with seeds [b"earnings_receipt", args.run_id.as_ref()] and payer authority) so Anchor will fail on duplicate receipts, and check/mark the receipt as processed (EarningsReceipt struct with a processed flag or just existence) before performing transfers, balance updates, and bumping run_count.
35-48:⚠️ Potential issue | 🔴 Critical
add_earningsstill missing authority validation againstSettlementConfig.Unlike
settle_earningswhich validatesauthority.key() == config.authority, this instruction accepts any signer. This allows arbitrary callers to fund the vault and manipulatepending_*balances, potentially inflating payouts or emitting fraudulentEarningsRecordedevents.Add a
configaccount and constrain the authority:🔒 Proposed fix
#[derive(Accounts)] #[instruction(args: AddEarningsArgs)] pub struct AddEarnings<'info> { - /// Settlement authority (Maschina multisig) — signs and funds the transfer. - #[account(mut)] + /// Settlement authority — must match config.authority. + #[account( + mut, + constraint = authority.key() == config.authority @ SettlementError::UnauthorisedSettlement, + )] pub authority: Signer<'info>, + + /// Global settlement config — holds the trusted authority key. + #[account( + seeds = [SettlementConfig::SEED], + bump = config.bump, + )] + pub config: Account<'info, SettlementConfig>,This requires importing
SettlementConfigin line 5.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@programs/settlement/src/instructions/add_earnings.rs` around lines 35 - 48, The add_earnings instruction is missing validation that the signer `authority` matches the configured multisig; add a `config: Account<'info, SettlementConfig>` to the instruction context and enforce the constraint that `config.authority == authority.key()` (mirroring settle_earnings). Update the account attributes for `config` to be read-only (or as required) and adjust the handler to use `config` instead of accepting any signer; also import `SettlementConfig` at the top of the file so the type is available. Ensure existing logic that updates `pending_*` and emits `EarningsRecorded` remains unchanged but now only executes when `authority` is validated against `config`.
🧹 Nitpick comments (5)
programs/settlement/src/instructions/initialize_config.rs (2)
3-3: Remove unused import.
SettlementErroris imported but not used in this file.Proposed fix
use anchor_lang::prelude::*; -use crate::errors::SettlementError; use crate::state::SettlementConfig;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@programs/settlement/src/instructions/initialize_config.rs` at line 3, The import SettlementError is unused in this file; remove the use stmt referencing SettlementError (the line "use crate::errors::SettlementError;") from initialize_config.rs so only actually used imports remain (verify functions like initialize_config or structs referenced in the file still compile and remove any other stale imports if flagged by the compiler).
41-48: Consider validating that payout pubkeys are not the default/system program.Accidentally passing
Pubkey::default()or the system program ID for treasury/developer/validators keys would brick settlement. A simple validation could prevent misconfiguration.Proposed validation
pub fn handler(ctx: Context<InitializeConfig>, args: InitializeConfigArgs) -> Result<()> { + // Sanity check: payout keys should not be default or system program + require!( + args.treasury_key != Pubkey::default() && args.treasury_key != System::id(), + SettlementError::InvalidPayoutKey + ); + require!( + args.developer_key != Pubkey::default() && args.developer_key != System::id(), + SettlementError::InvalidPayoutKey + ); + require!( + args.validators_key != Pubkey::default() && args.validators_key != System::id(), + SettlementError::InvalidPayoutKey + ); + let config = &mut ctx.accounts.config;This would require keeping the
SettlementErrorimport and addingInvalidPayoutKeyto the error enum.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@programs/settlement/src/instructions/initialize_config.rs` around lines 41 - 48, In handler (fn handler(ctx: Context<InitializeConfig>, args: InitializeConfigArgs)) add validations that args.treasury_key, args.developer_key, and args.validators_key are not Pubkey::default() and not equal to solana_program::system_program::ID; if any are invalid return Err(SettlementError::InvalidPayoutKey.into()). Keep existing assignments to config.authority and config.bump but perform these checks before setting config.* fields; ensure the SettlementError enum includes InvalidPayoutKey and that the SettlementError import is retained.scripts/setup-realms.ts (1)
61-82: Extract shared utilities to reduce duplication.
CLUSTER,getRpcUrl(), andloadKeypair()are duplicated fromsetup-mach-token.tswith identical logic. This also inherits the same issues: no runtime validation forCLUSTER, noSOLANA_WALLET_PATHsupport, and no tilde expansion.Consider extracting these to a shared module (e.g.,
scripts/lib/config.ts) to fix bugs in one place and maintain consistency.Example shared module
// scripts/lib/config.ts import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { Keypair } from "@solana/web3.js"; export type Cluster = "devnet" | "mainnet-beta" | "localnet"; export function getCluster(): Cluster { const value = (process.env.SOLANA_CLUSTER ?? "devnet").trim(); if (value !== "devnet" && value !== "mainnet-beta" && value !== "localnet") { throw new Error(`Invalid SOLANA_CLUSTER: ${value}`); } return value; } export function getRpcUrl(cluster: Cluster = getCluster()): string { if (cluster === "localnet") return "http://127.0.0.1:8899"; const apiKey = process.env.HELIUS_API_KEY; if (apiKey) { return cluster === "mainnet-beta" ? `https://mainnet.helius-rpc.com/?api-key=${apiKey}` : `https://devnet.helius-rpc.com/?api-key=${apiKey}`; } return cluster === "mainnet-beta" ? "https://api.mainnet-beta.solana.com" : "https://api.devnet.solana.com"; } export function loadKeypair(): Keypair { const configuredPath = process.env.ANCHOR_WALLET ?? process.env.SOLANA_WALLET_PATH ?? path.join(os.homedir(), ".config", "solana", "id.json"); const walletPath = configuredPath.startsWith("~/") ? path.join(os.homedir(), configuredPath.slice(2)) : configuredPath; const raw = fs.readFileSync(walletPath, "utf-8"); const bytes = JSON.parse(raw) as number[]; return Keypair.fromSecretKey(Uint8Array.from(bytes)); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/setup-realms.ts` around lines 61 - 82, Extract the duplicated CLUSTER, getRpcUrl(), and loadKeypair() logic into a shared module (e.g., scripts/lib/config.ts) and replace the in-file definitions with imports; in the shared module implement getCluster() that validates SOLANA_CLUSTER values ("devnet" | "mainnet-beta" | "localnet") and use that in getRpcUrl(cluster) to preserve existing behavior, update loadKeypair() to prefer SOLANA_WALLET_PATH then ANCHOR_WALLET, perform tilde (~) expansion for home dir, and throw clear errors on invalid/missing configs so fixes apply consistently across setup-realms.ts and setup-mach-token.ts.scripts/setup-mach-token.ts (1)
117-121: Move constant definitions before usage for clarity.
TYPE_SIZEandLENGTH_SIZEare used at line 117 but defined at lines 179-180. While JavaScript hoisting makes this work, it harms readability.Proposed fix
Move these constants to the Config section near the top:
// ─── Config ─────────────────────────────────────────────────────────────────── +// Token-2022 metadata size constants +const TYPE_SIZE = 2; +const LENGTH_SIZE = 4; + const MACH_TOKEN = {And remove them from the bottom of the file.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/setup-mach-token.ts` around lines 117 - 121, The constants TYPE_SIZE and LENGTH_SIZE are used when computing metadataExtension and mintLamports but are defined much later, harming readability; move the constant definitions for TYPE_SIZE and LENGTH_SIZE up into the Config/top section near other configuration constants so they are declared before they are referenced (affecting the computation of metadataExtension, mintLen via getMintLen([ExtensionType.MetadataPointer]) and the call to connection.getMinimumBalanceForRentExemption), and remove the duplicate definitions from the bottom of the file so there’s a single, top-level declaration.programs/settlement/src/instructions/settle_earnings.rs (1)
120-122: Verify signer seeds match the pool PDA derivation.The pool PDA is derived with
seeds = [b"pool", args.node_id.as_ref()](line 43). Here,&args.node_idyields&[u8; 16]which coerces to&[u8], so this is correct. However, for consistency and clarity with the accounts constraint, consider using.as_ref():- let pool_seeds: &[&[u8]] = &[b"pool", &args.node_id, &[pool.bump]]; + let pool_seeds: &[&[u8]] = &[b"pool", args.node_id.as_ref(), &[pool.bump]];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@programs/settlement/src/instructions/settle_earnings.rs` around lines 120 - 122, The PDA signer seeds in settle_earnings.rs use &args.node_id which works but should be replaced with args.node_id.as_ref() for clarity and to match the PDA derivation used elsewhere; update the pool_seeds declaration (and the signer_seeds usage) to use &[b"pool", args.node_id.as_ref(), &[pool.bump]] so the seeds and signer_seeds (pool_seeds, signer_seeds) clearly mirror the original PDA derivation and types.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/setup-realms.ts`:
- Around line 114-127: The call to withCreateRealm is missing the required
connection parameter and so all subsequent arguments are shifted; update the
call to pass your Connection instance as the second argument (so the order
becomes: instructions, connection, GOVERNANCE_PROGRAM_ID, programVersion,
REALM_NAME, realmAuthority (authority.publicKey), communityMint, payer
(authority.publicKey), ...), and verify MIN_TOKENS_TO_CREATE_GOVERNANCE and
MintMaxVoteWeightSource.FULL_SUPPLY_FRACTION are passed in their correct
positions per the withCreateRealm signature to restore proper argument
alignment.
In `@scripts/test-settlement-e2e.ts`:
- Around line 336-338: The log message uses the SOL-specific term "lamports";
update the console.log that prints `${totalOk ? "✓" : "✗"} Total distributed:
${total} / ${EARNINGS_AMOUNT} lamports` to use USDC terminology instead (e.g.,
"USDC (base units)" or "micro‑USDC") so it correctly reflects USDC's 6-decimal
base unit; change the literal "lamports" to the chosen USDC label and keep the
existing variables total, totalOk, and EARNINGS_AMOUNT unchanged.
- Around line 256-257: The operatorProgram is being constructed without an
explicit program ID, causing it to differ from the main program instance; update
the instantiation of operatorProgram (where Program, idl, and operatorProvider
are used) to pass the same explicit program ID as the main program (e.g., use
program.programId or the shared PROGRAM_ID constant as the second argument) and
move operatorProvider to the provider/third argument so operatorProgram uses the
exact same program ID as program.
- Around line 129-142: The Program constructor is not using the programId loaded
from SETTLEMENT_PROGRAM_ID so transactions may target the IDL's embedded
address; update the IDL's address field or override it before constructing the
Anchor Program: set idl.address = programId.toString() (or otherwise load/verify
settlement.json contains the same address) and then call new Program(idl as any,
provider) (also apply the same fix for operatorProgram created at the later
block). Additionally, add a runtime check that idl.address matches programId and
bail with an error if they differ to prevent silent mis-targeting.
- Around line 173-174: Replace the deprecated confirmTransaction(signature) call
by using the blockheight-based confirmation strategy: after obtaining airdropSig
from connection.requestAirdrop, call connection.getLatestBlockhash() to retrieve
{blockhash, lastValidBlockHeight} and then call connection.confirmTransaction({
signature: airdropSig, blockhash, lastValidBlockHeight }) (optionally with a
commitment) so the code uses the
BlockheightBasedTransactionConfirmationStrategy; update references around
airdropSig, connection.requestAirdrop, and connection.confirmTransaction
accordingly.
---
Duplicate comments:
In `@programs/settlement/src/instructions/add_earnings.rs`:
- Around line 16-28: The AddEarnings instruction currently only emits run_id in
an event and is vulnerable to replay because run_id is not persisted; add a
receipt PDA keyed by args.run_id to enforce idempotency by rejecting duplicate
runs before moving funds. Modify the AddEarnings handler to require an
Account<'info', EarningsReceipt> (initialized with seeds [b"earnings_receipt",
args.run_id.as_ref()] and payer authority) so Anchor will fail on duplicate
receipts, and check/mark the receipt as processed (EarningsReceipt struct with a
processed flag or just existence) before performing transfers, balance updates,
and bumping run_count.
- Around line 35-48: The add_earnings instruction is missing validation that the
signer `authority` matches the configured multisig; add a `config:
Account<'info, SettlementConfig>` to the instruction context and enforce the
constraint that `config.authority == authority.key()` (mirroring
settle_earnings). Update the account attributes for `config` to be read-only (or
as required) and adjust the handler to use `config` instead of accepting any
signer; also import `SettlementConfig` at the top of the file so the type is
available. Ensure existing logic that updates `pending_*` and emits
`EarningsRecorded` remains unchanged but now only executes when `authority` is
validated against `config`.
In `@programs/settlement/src/instructions/init_node_vault.rs`:
- Around line 29-44: The vault init currently accepts a caller-provided
usdc_mint allowing a malicious first initializer to bind the vault to a wrong
mint; add validation to ensure the mint equals the canonical USDC mint stored in
your config: extend SettlementConfig to include a canonical_usdc_mint field (set
via initialize_config), then in the init_node_vault handler check that the
provided usdc_mint.key() matches SettlementConfig::canonical_usdc_mint and
return an error if not; update any callers/tests to supply the canonical mint
and keep add_earnings/settle_earnings behavior unchanged but now safe because
vault/usdc_mint are validated.
In `@scripts/setup-devnet.ts`:
- Around line 28-41: The webhook URL construction can produce a double slash
when API_BASE_URL ends with a slash; normalize apiBaseUrl before building
webhookUrl by trimming any trailing slash (operate on the apiBaseUrl variable
obtained from process.env.API_BASE_URL) or use a URL-join approach when
composing the webhookUrl variable; update the logic where const apiBaseUrl is
validated and where const webhookUrl = `${apiBaseUrl}/webhooks/helius` is
defined (and keep getHeliusClient() usages unchanged) so webhookUrl always
becomes "<origin>/webhooks/helius" without duplicated slashes.
- Around line 10-14: The header comment claims the script "Verifies the
settlement program is deployed at SETTLEMENT_PROGRAM_ID" but no RPC/account
check exists; either remove or reword that claim or implement an actual
verification: locate the SETTLEMENT_PROGRAM_ID constant in
scripts/setup-devnet.ts and either change the header to say the script "assumes
the settlement program is deployed at SETTLEMENT_PROGRAM_ID" or add a
verification step using the Solana Connection (e.g., connection.getAccountInfo
or getProgramAccounts) to check for an account/executable at
SETTLEMENT_PROGRAM_ID and fail/log if missing before proceeding to the Helius
webhook registration and printing of HELIUS_WEBHOOK_ID.
In `@scripts/setup-mach-token.ts`:
- Around line 67-73: The loadKeypair function currently only reads ANCHOR_WALLET
and doesn't expand tilde paths; update loadKeypair to prefer SOLANA_WALLET_PATH
(fall back to ANCHOR_WALLET then the existing default), and ensure any path
starting with "~" is expanded to the user home directory (e.g., replace leading
"~" with os.homedir()) before passing to fs.readFileSync/Path APIs so
"~/.config/solana/id.json" resolves correctly; keep the rest of the logic (JSON
parse and Keypair.fromSecretKey) unchanged.
- Around line 52-65: The CLUSTER value (from SOLANA_CLUSTER) is not validated
and can silently map unknown strings to devnet; update getRpcUrl (or the
initialization of CLUSTER) to validate the environment value against an explicit
allowed set (e.g., "localnet", "devnet", "mainnet-beta"), and if the value is
invalid either throw an Error or log a clear message and exit so misconfigured
deployments fail fast; make sure to reference CLUSTER and getRpcUrl in the
message and state the supported values so callers know the valid options.
- Around line 172-176: The printed explorer URL and .env hints are hardcoded to
devnet; update the console output to use the CLUSTER value (read from
process.env.CLUSTER or the existing cluster variable) and fall back to "devnet"
if unset, so the Explorer link (constructed with mintAddress and the cluster
query param) and the suggested SOLANA_CLUSTER value reflect the actual cluster
(e.g., "devnet" or "mainnet-beta"); change the three console.log lines that
reference mintAddress and the hardcoded "devnet" to interpolate the resolved
cluster variable instead.
---
Nitpick comments:
In `@programs/settlement/src/instructions/initialize_config.rs`:
- Line 3: The import SettlementError is unused in this file; remove the use stmt
referencing SettlementError (the line "use crate::errors::SettlementError;")
from initialize_config.rs so only actually used imports remain (verify functions
like initialize_config or structs referenced in the file still compile and
remove any other stale imports if flagged by the compiler).
- Around line 41-48: In handler (fn handler(ctx: Context<InitializeConfig>,
args: InitializeConfigArgs)) add validations that args.treasury_key,
args.developer_key, and args.validators_key are not Pubkey::default() and not
equal to solana_program::system_program::ID; if any are invalid return
Err(SettlementError::InvalidPayoutKey.into()). Keep existing assignments to
config.authority and config.bump but perform these checks before setting
config.* fields; ensure the SettlementError enum includes InvalidPayoutKey and
that the SettlementError import is retained.
In `@programs/settlement/src/instructions/settle_earnings.rs`:
- Around line 120-122: The PDA signer seeds in settle_earnings.rs use
&args.node_id which works but should be replaced with args.node_id.as_ref() for
clarity and to match the PDA derivation used elsewhere; update the pool_seeds
declaration (and the signer_seeds usage) to use &[b"pool",
args.node_id.as_ref(), &[pool.bump]] so the seeds and signer_seeds (pool_seeds,
signer_seeds) clearly mirror the original PDA derivation and types.
In `@scripts/setup-mach-token.ts`:
- Around line 117-121: The constants TYPE_SIZE and LENGTH_SIZE are used when
computing metadataExtension and mintLamports but are defined much later, harming
readability; move the constant definitions for TYPE_SIZE and LENGTH_SIZE up into
the Config/top section near other configuration constants so they are declared
before they are referenced (affecting the computation of metadataExtension,
mintLen via getMintLen([ExtensionType.MetadataPointer]) and the call to
connection.getMinimumBalanceForRentExemption), and remove the duplicate
definitions from the bottom of the file so there’s a single, top-level
declaration.
In `@scripts/setup-realms.ts`:
- Around line 61-82: Extract the duplicated CLUSTER, getRpcUrl(), and
loadKeypair() logic into a shared module (e.g., scripts/lib/config.ts) and
replace the in-file definitions with imports; in the shared module implement
getCluster() that validates SOLANA_CLUSTER values ("devnet" | "mainnet-beta" |
"localnet") and use that in getRpcUrl(cluster) to preserve existing behavior,
update loadKeypair() to prefer SOLANA_WALLET_PATH then ANCHOR_WALLET, perform
tilde (~) expansion for home dir, and throw clear errors on invalid/missing
configs so fixes apply consistently across setup-realms.ts and
setup-mach-token.ts.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e4d5d22a-1c8a-4c3f-b886-e8a6d7fdb57d
⛔ Files ignored due to path filters (2)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlprograms/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (31)
.env.example.gitignoreapps/docs/src/content/introduction.mdxapps/docs/src/routes/introduction.tsxapps/web/src/routes/about.tsxapps/web/src/routes/careers.tsxapps/web/src/routes/index.tsxapps/web/src/routes/marketplace.tsxapps/web/src/routes/press-kit.tsxapps/web/src/routes/security.tsxascii-logo.txtpackages/chain/package.jsonpackages/marketplace/src/marketplace.test.tsprograms/Anchor.tomlprograms/Cargo.tomlprograms/settlement/Cargo.tomlprograms/settlement/src/errors.rsprograms/settlement/src/instructions/add_earnings.rsprograms/settlement/src/instructions/init_node_vault.rsprograms/settlement/src/instructions/initialize_config.rsprograms/settlement/src/instructions/mod.rsprograms/settlement/src/instructions/register_node.rsprograms/settlement/src/instructions/settle_earnings.rsprograms/settlement/src/lib.rsprograms/settlement/src/state.rsprograms/settlement/target/idl/settlement.jsonscripts/discord-scaffold.mjsscripts/setup-devnet.tsscripts/setup-mach-token.tsscripts/setup-realms.tsscripts/test-settlement-e2e.ts
✅ Files skipped from review due to trivial changes (13)
- ascii-logo.txt
- .gitignore
- apps/docs/src/content/introduction.mdx
- apps/web/src/routes/careers.tsx
- apps/web/src/routes/press-kit.tsx
- apps/web/src/routes/about.tsx
- apps/docs/src/routes/introduction.tsx
- apps/web/src/routes/security.tsx
- apps/web/src/routes/marketplace.tsx
- programs/Cargo.toml
- apps/web/src/routes/index.tsx
- packages/chain/package.json
- programs/settlement/target/idl/settlement.json
🚧 Files skipped from review as they are similar to previous changes (6)
- programs/settlement/Cargo.toml
- programs/settlement/src/instructions/mod.rs
- programs/settlement/src/errors.rs
- programs/Anchor.toml
- packages/marketplace/src/marketplace.test.ts
- scripts/discord-scaffold.mjs
| const instructions: Parameters<typeof withCreateRealm>[0] = []; | ||
|
|
||
| const realmAddress = await withCreateRealm( | ||
| instructions, | ||
| GOVERNANCE_PROGRAM_ID, | ||
| programVersion, | ||
| REALM_NAME, | ||
| authority.publicKey, // realm authority | ||
| communityMint, | ||
| authority.publicKey, // payer | ||
| undefined, // no council mint — MACH-only governance | ||
| MintMaxVoteWeightSource.FULL_SUPPLY_FRACTION, // all MACH supply counts for quorum | ||
| MIN_TOKENS_TO_CREATE_GOVERNANCE, | ||
| ); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check the installed version of `@solana/spl-governance`
cat package.json | jq '.dependencies["@solana/spl-governance"] // .devDependencies["@solana/spl-governance"]'
# Search for withCreateRealm usage patterns in the codebase
rg -n "withCreateRealm" --type=ts -C3Repository: RustMunkey/maschina
Length of output: 921
🏁 Script executed:
#!/bin/bash
# Read the actual package.json to see all dependencies
head -100 package.json
# Check for lock files that might contain version info
ls -la | grep -E "package-lock|yarn.lock|pnpm-lock"
# Check if there's a workspace configuration
if [ -f "pnpm-workspace.yaml" ] || [ -f "lerna.json" ]; then
echo "Found workspace config"
fiRepository: RustMunkey/maschina
Length of output: 5554
🏁 Script executed:
#!/bin/bash
# Look for `@solana/spl-governance` in lock files
if [ -f "package-lock.json" ]; then
rg -o '"@solana/spl-governance"[^}]*"version"[^,]*' package-lock.json | head -5
elif [ -f "yarn.lock" ]; then
rg '@solana/spl-governance@' yarn.lock | head -5
elif [ -f "pnpm-lock.yaml" ]; then
rg '@solana/spl-governance' pnpm-lock.yaml | head -5
fiRepository: RustMunkey/maschina
Length of output: 222
🏁 Script executed:
#!/bin/bash
# Try to find the actual type signature of withCreateRealm
# by searching for .d.ts files or looking at node_modules
find . -name "*.d.ts" -path "*spl-governance*" 2>/dev/null | head -5
# If node_modules exists, check the types directly
if [ -d "node_modules/@solana/spl-governance" ]; then
ls node_modules/@solana/spl-governance/ | head -20
fiRepository: RustMunkey/maschina
Length of output: 45
🏁 Script executed:
#!/bin/bash
# Search for any TypeScript type definitions or imports related to withCreateRealm
rg -n "withCreateRealm" -B5 -A10 scripts/setup-realms.ts
# Look for any tsconfig or type configuration
find . -maxdepth 2 -name "tsconfig.json" -o -name "tsconfig*.json" 2>/dev/null | head -5
# Search for any type stub files or .d.ts files in the repo
find . -name "*.d.ts" -not -path "*/node_modules/*" 2>/dev/null | head -10Repository: RustMunkey/maschina
Length of output: 1401
🌐 Web query:
@solana/spl-governance 0.3.28 withCreateRealm function signature
💡 Result:
The function withCreateRealm in @solana/spl-governance 0.3.28 is an instruction builder that appends a createRealm instruction to a provided array of TransactionInstruction[]. It returns the PDA address of the created realm. Signature (TypeScript): withCreateRealm( instructions: TransactionInstruction[], connection: Connection, programId: PublicKey, programVersion: 'v1' | 'legacy', name: string, realmAuthority: PublicKey, communityMint: PublicKey, payer: PublicKey, councilMint?: PublicKey, communityMintMaxVoteWeightSource?: MintMaxVoteWeightSource, additionalAccounts?: GoverningTokenConfigAccountArgs[] ): Promise Example usage pattern from sources: const instructions: TransactionInstruction[] = []; const realmAddress = await withCreateRealm(instructions, connection, programId, 'v1', 'MyDAO', authority, communityMint, payer); This follows the SDK's builder pattern where functions prefixed with with modify an instructions array. Confirmed present in package exports via Deno issue listing default exports including withCreateRealm. Similar Realms SDK documentation shows exact usage for realm creation. NPM file listing shows related createRealm functions in lib/governance.
Citations:
- 1: https://registry.npmjs.org/%40solana%2Fspl-governance
- 2: npm compat: Unable to use @solana/spl-governance denoland/deno#23458
- 3: https://docs.realms.today/developer-resources
Fix withCreateRealm function call: missing connection parameter and misaligned arguments.
The function signature in @solana/spl-governance 0.3.28 expects withCreateRealm(instructions, connection, programId, programVersion, name, realmAuthority, communityMint, payer, ...), but the call at lines 116–126 is missing the connection parameter and has all subsequent arguments shifted. The connection should be the second argument. Additionally, the type annotation Parameters<typeof withCreateRealm>[0] only validates the first parameter; it does not catch the misalignment of the remaining arguments.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/setup-realms.ts` around lines 114 - 127, The call to withCreateRealm
is missing the required connection parameter and so all subsequent arguments are
shifted; update the call to pass your Connection instance as the second argument
(so the order becomes: instructions, connection, GOVERNANCE_PROGRAM_ID,
programVersion, REALM_NAME, realmAuthority (authority.publicKey), communityMint,
payer (authority.publicKey), ...), and verify MIN_TOKENS_TO_CREATE_GOVERNANCE
and MintMaxVoteWeightSource.FULL_SUPPLY_FRACTION are passed in their correct
positions per the withCreateRealm signature to restore proper argument
alignment.
| const programId = new PublicKey( | ||
| process.env.SETTLEMENT_PROGRAM_ID ?? "11111111111111111111111111111111", | ||
| ); | ||
|
|
||
| if (programId.equals(new PublicKey("11111111111111111111111111111111"))) { | ||
| console.error("SETTLEMENT_PROGRAM_ID not set. Deploy the program first."); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| // biome-ignore lint/suspicious/noExplicitAny: Anchor IDL requires any | ||
| const program = new Program(idl as any, provider); | ||
|
|
||
| console.log(`Authority: ${authority.publicKey.toBase58()}`); | ||
| console.log(`Program ID: ${programId.toBase58()}`); |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
What is the Program constructor signature in @coral-xyz/anchor version 0.30.0 and how do you pass a custom program ID?
💡 Result:
In @coral-xyz/anchor version 0.30.0, the Program constructor signature is: new Program(idl: IDL, provider?: Provider, coder?: Coder<string, string>, getCustomResolver?: (instruction: IdlInstruction) => undefined | CustomAccountResolver): Program To pass a custom program ID, use the static Program.at method: await Program.at(address: PublicKey | string, provider?: Provider): Promise<Program> This fetches the IDL from the given program address on-chain (requires prior anchor idl init) and creates the Program instance. For local/offline use with a custom ID, manually set the "address" field in the IDL JSON to the base58-encoded PublicKey string before passing to the constructor, as the separate programId parameter was removed in 0.30.0 and the ID is now read from idl.address.
Citations:
- 1: https://coral-xyz.github.io/anchor/ts/classes/Program.html
- 2: https://www.anchor-lang.com/docs/updates/release-notes/0-30-0
- 3: https://mintlify.com/solana-foundation/anchor/api/typescript/program
🏁 Script executed:
wc -l scripts/test-settlement-e2e.tsRepository: RustMunkey/maschina
Length of output: 97
🏁 Script executed:
sed -n '1,50p' scripts/test-settlement-e2e.tsRepository: RustMunkey/maschina
Length of output: 1948
🏁 Script executed:
sed -n '125,165p' scripts/test-settlement-e2e.tsRepository: RustMunkey/maschina
Length of output: 1583
🏁 Script executed:
sed -n '250,270p' scripts/test-settlement-e2e.tsRepository: RustMunkey/maschina
Length of output: 988
🏁 Script executed:
sed -n '330,345p' scripts/test-settlement-e2e.tsRepository: RustMunkey/maschina
Length of output: 729
🏁 Script executed:
sed -n '60,120p' scripts/test-settlement-e2e.tsRepository: RustMunkey/maschina
Length of output: 2032
Program ID from environment is not used by the Anchor Program constructor.
The programId is loaded from the SETTLEMENT_PROGRAM_ID environment variable but is only used for logging (line 142). The Program constructor on line 139 does not receive it. In Anchor 0.30.0, the program ID is read from the IDL's address field instead.
If the IDL's address field differs from the environment variable, transactions will target the IDL's address rather than the intended deployment, silently breaking the test on devnet/mainnet.
Correct solutions for Anchor 0.30.0:
-
Ensure the IDL's
addressfield is set correctly: The loaded IDL should contain anaddressfield matching the deployed program ID. Verify thatsettlement.jsonincludes this field. -
Programmatically override the IDL address: Before passing to the constructor, set the IDL address to match the environment variable:
+ idl.address = programId.toBase58(); const program = new Program(idl as any, provider); -
Validate address consistency: Verify the IDL address matches the environment variable before proceeding:
+ if (idl.address && !new PublicKey(idl.address).equals(programId)) { + console.error(`IDL address ${idl.address} does not match SETTLEMENT_PROGRAM_ID`); + process.exit(1); + }
The same issue exists at line 257 with operatorProgram.
Minor: Line 337 refers to "lamports" but these are USDC amounts with 6 decimal places, not SOL. Consider updating the label for clarity.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const programId = new PublicKey( | |
| process.env.SETTLEMENT_PROGRAM_ID ?? "11111111111111111111111111111111", | |
| ); | |
| if (programId.equals(new PublicKey("11111111111111111111111111111111"))) { | |
| console.error("SETTLEMENT_PROGRAM_ID not set. Deploy the program first."); | |
| process.exit(1); | |
| } | |
| // biome-ignore lint/suspicious/noExplicitAny: Anchor IDL requires any | |
| const program = new Program(idl as any, provider); | |
| console.log(`Authority: ${authority.publicKey.toBase58()}`); | |
| console.log(`Program ID: ${programId.toBase58()}`); | |
| const programId = new PublicKey( | |
| process.env.SETTLEMENT_PROGRAM_ID ?? "11111111111111111111111111111111", | |
| ); | |
| if (programId.equals(new PublicKey("11111111111111111111111111111111"))) { | |
| console.error("SETTLEMENT_PROGRAM_ID not set. Deploy the program first."); | |
| process.exit(1); | |
| } | |
| // biome-ignore lint/suspicious/noExplicitAny: Anchor IDL requires any | |
| idl.address = programId.toBase58(); | |
| const program = new Program(idl as any, provider); | |
| console.log(`Authority: ${authority.publicKey.toBase58()}`); | |
| console.log(`Program ID: ${programId.toBase58()}`); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/test-settlement-e2e.ts` around lines 129 - 142, The Program
constructor is not using the programId loaded from SETTLEMENT_PROGRAM_ID so
transactions may target the IDL's embedded address; update the IDL's address
field or override it before constructing the Anchor Program: set idl.address =
programId.toString() (or otherwise load/verify settlement.json contains the same
address) and then call new Program(idl as any, provider) (also apply the same
fix for operatorProgram created at the later block). Additionally, add a runtime
check that idl.address matches programId and bail with an error if they differ
to prevent silent mis-targeting.
| const airdropSig = await connection.requestAirdrop(operatorKeypair.publicKey, 1e9); | ||
| await connection.confirmTransaction(airdropSig); |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
What is the recommended way to confirm a transaction in @solana/web3.js 1.90+ and is the single signature overload deprecated?
💡 Result:
In @solana/web3.js version 1.90+, the recommended way to confirm a transaction is to use connection.confirmTransaction with a BlockheightBasedTransactionConfirmationStrategy object, which includes the transaction signature, the blockhash, and lastValidBlockHeight from the transaction. This replaced the older string-only signature overload, which is deprecated. Example: const latestBlockhash = await connection.getLatestBlockhash('confirmed'); const signature = await connection.sendTransaction(transaction, signers, options); // Confirm using strategy await connection.confirmTransaction({ signature, blockhash: latestBlockhash.blockhash, lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, }, 'confirmed'); For convenience, use sendAndConfirmTransaction(connection, transaction, signers, options), which internally handles signing, sending, and confirming using the above strategy if the transaction has recentBlockhash and lastValidBlockHeight set. The single-signature string overload confirmTransaction(signature, commitment) is deprecated; it lacks blockhash info for proper expiration checking and may fail or hang for older/recently confirmed transactions. Use the strategy overload instead.
Citations:
- 1: https://stackoverflow.com/questions/72330340/solana-web3-confirmtransaction-deprecated-using-a-transactionconfirmationconfig
- 2: https://solana.stackexchange.com/questions/7863/solana-web3-js-problems-with-confirmtransaction
- 3: https://solana-labs.github.io/solana-web3.js/classes/Connection.html
- 4: https://github.com/solana-labs/solana-web3.js/blob/d7ed86a68c21238d8c66f9b1f9b3a7a2e9a92c8e/src/util/send-and-confirm-transaction.ts
- 5: https://solana.stackexchange.com/questions/6062/how-to-properly-wait-for-a-transaction-to-confirm
🏁 Script executed:
# Find and check package.json or similar files for `@solana/web3.js` version
fd -e package.json -e package-lock.json | head -5Repository: RustMunkey/maschina
Length of output: 45
🏁 Script executed:
# Check the actual code at lines 173-174 in the file
cat -n scripts/test-settlement-e2e.ts | sed -n '170,180p'Repository: RustMunkey/maschina
Length of output: 644
🏁 Script executed:
# Use git ls-files to find package.json
git ls-files | grep package.jsonRepository: RustMunkey/maschina
Length of output: 1938
🏁 Script executed:
# Also search for any lock files
git ls-files | grep -E "(package-lock\.json|yarn\.lock|pnpm-lock\.yaml)"Repository: RustMunkey/maschina
Length of output: 77
🏁 Script executed:
# Find the test-settlement-e2e.ts file
git ls-files | grep test-settlement-e2e.tsRepository: RustMunkey/maschina
Length of output: 93
🏁 Script executed:
# Check if there's a scripts directory at root or in a specific package
ls -la scripts/ 2>/dev/null || echo "No scripts at root"Repository: RustMunkey/maschina
Length of output: 1110
🏁 Script executed:
# Check root package.json for `@solana/web3.js`
cat package.json | grep -A 50 dependencies | grep -E "(solana|@solana)"Repository: RustMunkey/maschina
Length of output: 45
🏁 Script executed:
# If not in root, check pnpm-lock.yaml for `@solana/web3.js`
grep "@solana/web3.js" pnpm-lock.yaml | head -10Repository: RustMunkey/maschina
Length of output: 724
🏁 Script executed:
# Get full root package.json to see all dependencies
head -200 package.jsonRepository: RustMunkey/maschina
Length of output: 11218
🏁 Script executed:
# Also check if scripts have a specific tsconfig or package setup
cat scripts/tsconfig.json 2>/dev/null || echo "No tsconfig in scripts"Repository: RustMunkey/maschina
Length of output: 85
🏁 Script executed:
# Check imports in test-settlement-e2e.ts to see where Connection comes from
head -50 scripts/test-settlement-e2e.tsRepository: RustMunkey/maschina
Length of output: 1948
🏁 Script executed:
# Check which packages declare `@solana/web3.js` as dependency
grep -r "@solana/web3.js" package.json | head -20Repository: RustMunkey/maschina
Length of output: 45
🏁 Script executed:
# Also check if there's a tests package that might own this script
cat tests/package.json 2>/dev/null | head -100 || echo "No tests/package.json"Repository: RustMunkey/maschina
Length of output: 525
🏁 Script executed:
# Search all package.json files for `@solana/web3.js` dependency
find . -name package.json -exec grep -l "@solana/web3.js" {} \;Repository: RustMunkey/maschina
Length of output: 92
🏁 Script executed:
# Check packages/chain/package.json for `@solana/web3.js` version
cat packages/chain/package.json | grep -A 50 dependenciesRepository: RustMunkey/maschina
Length of output: 474
Update transaction confirmation to use the BlockheightBasedTransactionConfirmationStrategy pattern.
The confirmTransaction(signature) overload is deprecated in @solana/web3.js 1.90+ (this project uses ^1.98.0). Use the recommended pattern with blockhash and lastValidBlockHeight instead.
♻️ Recommended update
const airdropSig = await connection.requestAirdrop(operatorKeypair.publicKey, 1e9);
- await connection.confirmTransaction(airdropSig);
+ const latestBlockhash = await connection.getLatestBlockhash();
+ await connection.confirmTransaction({
+ signature: airdropSig,
+ ...latestBlockhash,
+ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const airdropSig = await connection.requestAirdrop(operatorKeypair.publicKey, 1e9); | |
| await connection.confirmTransaction(airdropSig); | |
| const airdropSig = await connection.requestAirdrop(operatorKeypair.publicKey, 1e9); | |
| const latestBlockhash = await connection.getLatestBlockhash(); | |
| await connection.confirmTransaction({ | |
| signature: airdropSig, | |
| ...latestBlockhash, | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/test-settlement-e2e.ts` around lines 173 - 174, Replace the
deprecated confirmTransaction(signature) call by using the blockheight-based
confirmation strategy: after obtaining airdropSig from
connection.requestAirdrop, call connection.getLatestBlockhash() to retrieve
{blockhash, lastValidBlockHeight} and then call connection.confirmTransaction({
signature: airdropSig, blockhash, lastValidBlockHeight }) (optionally with a
commitment) so the code uses the
BlockheightBasedTransactionConfirmationStrategy; update references around
airdropSig, connection.requestAirdrop, and connection.confirmTransaction
accordingly.
| // biome-ignore lint/suspicious/noExplicitAny: Anchor IDL requires any | ||
| const operatorProgram = new Program(idl as any, operatorProvider); |
There was a problem hiding this comment.
Same program ID issue applies here.
The operatorProgram also needs to use the same explicit program ID to ensure consistency with the main program instance.
🔧 Proposed fix
- const operatorProgram = new Program(idl as any, operatorProvider);
+ const operatorProgram = new Program(idl as any, programId, operatorProvider);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // biome-ignore lint/suspicious/noExplicitAny: Anchor IDL requires any | |
| const operatorProgram = new Program(idl as any, operatorProvider); | |
| // biome-ignore lint/suspicious/noExplicitAny: Anchor IDL requires any | |
| const operatorProgram = new Program(idl as any, programId, operatorProvider); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/test-settlement-e2e.ts` around lines 256 - 257, The operatorProgram
is being constructed without an explicit program ID, causing it to differ from
the main program instance; update the instantiation of operatorProgram (where
Program, idl, and operatorProvider are used) to pass the same explicit program
ID as the main program (e.g., use program.programId or the shared PROGRAM_ID
constant as the second argument) and move operatorProvider to the provider/third
argument so operatorProgram uses the exact same program ID as program.
| console.log( | ||
| `\n ${totalOk ? "✓" : "✗"} Total distributed: ${total} / ${EARNINGS_AMOUNT} lamports`, | ||
| ); |
There was a problem hiding this comment.
Terminology: "lamports" is incorrect for USDC.
USDC uses 6 decimal places; the smallest unit is typically called "micro-USDC" or just "USDC base units." "Lamports" specifically refers to SOL's smallest unit (1 SOL = 10⁹ lamports).
📝 Suggested fix
- `\n ${totalOk ? "✓" : "✗"} Total distributed: ${total} / ${EARNINGS_AMOUNT} lamports`,
+ `\n ${totalOk ? "✓" : "✗"} Total distributed: ${total} / ${EARNINGS_AMOUNT} (USDC base units)`,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| console.log( | |
| `\n ${totalOk ? "✓" : "✗"} Total distributed: ${total} / ${EARNINGS_AMOUNT} lamports`, | |
| ); | |
| console.log( | |
| `\n ${totalOk ? "✓" : "✗"} Total distributed: ${total} / ${EARNINGS_AMOUNT} (USDC base units)`, | |
| ); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/test-settlement-e2e.ts` around lines 336 - 338, The log message uses
the SOL-specific term "lamports"; update the console.log that prints `${totalOk
? "✓" : "✗"} Total distributed: ${total} / ${EARNINGS_AMOUNT} lamports` to use
USDC terminology instead (e.g., "USDC (base units)" or "micro‑USDC") so it
correctly reflects USDC's 6-decimal base unit; change the literal "lamports" to
the chosen USDC label and keep the existing variables total, totalOk, and
EARNINGS_AMOUNT unchanged.
There was a problem hiding this comment.
Actionable comments posted: 7
♻️ Duplicate comments (2)
scripts/setup-mach-token.ts (2)
67-70:⚠️ Potential issue | 🟠 MajorUse
SOLANA_WALLET_PATHand expand~/before reading keypair file.The script still ignores
SOLANA_WALLET_PATHfrom.env.example, and Node won’t expand a leading tilde automatically.Proposed fix
+function resolveWalletPath(): string { + const configured = + process.env.SOLANA_WALLET_PATH ?? + process.env.ANCHOR_WALLET ?? + path.join(os.homedir(), ".config", "solana", "id.json"); + + return configured.startsWith("~/") + ? path.join(os.homedir(), configured.slice(2)) + : configured; +} + function loadKeypair(): Keypair { - const walletPath = - process.env.ANCHOR_WALLET ?? path.join(os.homedir(), ".config", "solana", "id.json"); + const walletPath = resolveWalletPath(); const raw = fs.readFileSync(walletPath, "utf-8"); const bytes = JSON.parse(raw) as number[]; return Keypair.fromSecretKey(Uint8Array.from(bytes)); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/setup-mach-token.ts` around lines 67 - 70, The loadKeypair() function currently reads walletPath from ANCHOR_WALLET and does not handle SOLANA_WALLET_PATH or a leading tilde; update loadKeypair() to prefer process.env.SOLANA_WALLET_PATH if set, and expand a leading "~" to the user's home directory (e.g., replace a starting "~/" with os.homedir() + "/") before calling fs.readFileSync so the path resolves correctly on Node.
52-65:⚠️ Potential issue | 🟠 MajorValidate
SOLANA_CLUSTERand stop hardcoding devnet in user-facing output.Invalid cluster values currently fall through and the final explorer/.env output still advertises devnet even when another cluster is used.
Proposed fix
-const CLUSTER = process.env.SOLANA_CLUSTER ?? "devnet"; +function parseCluster(): "devnet" | "mainnet-beta" | "localnet" { + const value = (process.env.SOLANA_CLUSTER ?? "devnet").trim(); + if (value !== "devnet" && value !== "mainnet-beta" && value !== "localnet") { + throw new Error(`Invalid SOLANA_CLUSTER: ${value}`); + } + return value; +} + +const CLUSTER = parseCluster(); @@ - if (balance < 0.1 * 1e9) { - console.error("Insufficient SOL. Run: solana airdrop 2 --url devnet"); + if (balance < 0.1 * 1e9) { + const topUpHint = + CLUSTER === "mainnet-beta" + ? "Fund this wallet with mainnet SOL." + : `Run: solana airdrop 2 --url ${CLUSTER}`; + console.error(`Insufficient SOL. ${topUpHint}`); process.exit(1); } @@ - console.log(` Explorer: https://explorer.solana.com/address/${mintAddress}?cluster=devnet`); + const explorerSuffix = CLUSTER === "mainnet-beta" ? "" : `?cluster=${CLUSTER}`; + console.log(` Explorer: https://explorer.solana.com/address/${mintAddress}${explorerSuffix}`); @@ - console.log(" SOLANA_CLUSTER=devnet"); + console.log(` SOLANA_CLUSTER=${CLUSTER}`);Also applies to: 87-89, 172-175
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/setup-mach-token.ts` around lines 52 - 65, Validate the SOLANA_CLUSTER value and stop hardcoding "devnet" in outputs: add an explicit whitelist check for CLUSTER (accept only "localnet", "devnet", "mainnet-beta") and either default to "devnet" or throw a clear error if the env is invalid, update getRpcUrl() to use the validated CLUSTER value, and replace any hardcoded "devnet" strings in the user-facing explorer/.env output blocks (the sections around lines 87-89 and 172-175) so they print the actual CLUSTER value rather than always showing devnet; reference the CLUSTER constant and getRpcUrl when adjusting those output generation sites.
🧹 Nitpick comments (1)
.env.example (1)
164-164: KeepPORTaligned withAPI_PORTif it is an alias.This line sets
PORT=3000whileAPI_PORTis already4000, which creates conflicting defaults for services that read different keys.Proposed fix
-PORT=3000 # API listen port (alias for API_PORT in some services) +PORT=4000 # API listen port (alias for API_PORT in some services)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.env.example at line 164, PORT is currently set to 3000 but API_PORT is 4000, causing conflicting defaults; change the PORT default to match API_PORT (set PORT=4000) or remove the duplicate and add a comment that PORT is an alias of API_PORT so both keys stay consistent (update the PORT line and/or comment referencing PORT and API_PORT).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.env.example:
- Around line 46-48: The .env example entries (e.g., HELIUS_API_KEY,
HELIUS_WEBHOOK_ID, HELIUS_WEBHOOK_SECRET) use extra spacing around the '=' which
triggers dotenv-linter; normalize them to the standard dotenv assignment format
with no spaces around '=', e.g., KEY=VALUE or KEY= (empty value) and keep any
explanatory comments after a single space following the value; apply the same
normalization to the other flagged blocks (lines around the other HELIUS entries
and the ranges noted: 56-58, 64, 162-166).
In `@scripts/setup-mach-token.ts`:
- Around line 117-121: The code computes metadataExtension and mintLen but only
uses mintLen when allocating the mint account, causing insufficient
space/lamports; create a single fullMintLen = mintLen + metadataExtension (or
reuse metadataExtension when calling createAccount) and use fullMintLen both
when calling getMinimumBalanceForRentExemption (mintLamports) and when passing
the space/size argument to createAccount so the created account has room for the
metadata extension; update any places that referenced mintLen for allocation to
use fullMintLen (symbols: metadataExtension, mintLen, mintLamports,
createAccount).
- Around line 94-100: Replace the use of new URL(import.meta.url).pathname with
fileURLToPath(import.meta.url) to get a correct platform path and ensure the
target wallets directory exists before writing: compute mintKeypairPath using
fileURLToPath(import.meta.url), call fs.mkdirSync(path.dirname(mintKeypairPath),
{ recursive: true }) (or equivalent) to create the wallets directory if missing,
then call fs.writeFileSync(mintKeypairPath,
JSON.stringify(Array.from(mintKeypair.secretKey))). Ensure references to
mintKeypairPath, import.meta.url, fileURLToPath, fs.mkdirSync, and
fs.writeFileSync are updated accordingly.
In `@scripts/upload-arweave.ts`:
- Around line 52-54: Update the keypair loader to honor SOLANA_WALLET_PATH
(fallback to ANCHOR_WALLET) and expand leading "~/" to the user home directory;
specifically, in loadKeypairPath (and the similar loader around lines 69-71)
check process.env.SOLANA_WALLET_PATH first, then process.env.ANCHOR_WALLET, and
if the chosen path starts with "~/" replace that prefix with os.homedir() (using
path.join to construct the remainder) so both env names and tilde-prefixed paths
are handled correctly.
- Around line 26-34: The code allows invalid SOLANA_CLUSTER values and prints a
hardcoded "devnet" in summary; validate and normalize CLUSTER by checking
process.env.SOLANA_CLUSTER against allowed values ("mainnet-beta", "devnet",
"localnet" or your supported set) in the CLUSTER constant initialization (or
throw/exit on unknown values), update getIrysUrl() to explicitly handle only
allowed clusters and throw or error for unsupported ones, and change any
summary/print logic that currently hardcodes "devnet" to use the CLUSTER
variable so the printed command reflects the actual selected cluster (refer to
CLUSTER, getIrysUrl, and the summary print site).
- Around line 105-109: The code assumes metadata.properties.files[0] exists
before mutating it; add a guard that validates the parsed metadata shape after
JSON.parse(metadataRaw) — ensure metadata is an object, metadata.properties is
an object, metadata.properties.files is an array and files[0] exists (or
initialize properties/files to sensible defaults) before setting metadata.image
and metadata.properties.files[0].uri; if validation fails, throw a clear error
that includes METADATA_PATH and the missing field so callers know the JSON is
malformed.
- Around line 28-30: The current ESM path resolution using path.dirname(new
URL(import.meta.url).pathname) is fragile on Windows; import fileURLToPath from
'url' and replace that expression with
path.dirname(fileURLToPath(import.meta.url)) so ASSETS_DIR, IMAGE_PATH, and
METADATA_PATH are computed using a platform-native path; also add the import {
fileURLToPath } from 'url' at the top of the module so the new call resolves
correctly.
---
Duplicate comments:
In `@scripts/setup-mach-token.ts`:
- Around line 67-70: The loadKeypair() function currently reads walletPath from
ANCHOR_WALLET and does not handle SOLANA_WALLET_PATH or a leading tilde; update
loadKeypair() to prefer process.env.SOLANA_WALLET_PATH if set, and expand a
leading "~" to the user's home directory (e.g., replace a starting "~/" with
os.homedir() + "/") before calling fs.readFileSync so the path resolves
correctly on Node.
- Around line 52-65: Validate the SOLANA_CLUSTER value and stop hardcoding
"devnet" in outputs: add an explicit whitelist check for CLUSTER (accept only
"localnet", "devnet", "mainnet-beta") and either default to "devnet" or throw a
clear error if the env is invalid, update getRpcUrl() to use the validated
CLUSTER value, and replace any hardcoded "devnet" strings in the user-facing
explorer/.env output blocks (the sections around lines 87-89 and 172-175) so
they print the actual CLUSTER value rather than always showing devnet; reference
the CLUSTER constant and getRpcUrl when adjusting those output generation sites.
---
Nitpick comments:
In @.env.example:
- Line 164: PORT is currently set to 3000 but API_PORT is 4000, causing
conflicting defaults; change the PORT default to match API_PORT (set PORT=4000)
or remove the duplicate and add a comment that PORT is an alias of API_PORT so
both keys stay consistent (update the PORT line and/or comment referencing PORT
and API_PORT).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b7981f1b-11c8-4eb4-aed8-c9f161426167
⛔ Files ignored due to path filters (2)
assets/mach-token.pngis excluded by!**/*.pngpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (6)
.env.exampleassets/mach-token-metadata.jsonpackage.jsonscripts/setup-mach-token.tsscripts/setup-realms.tsscripts/upload-arweave.ts
✅ Files skipped from review due to trivial changes (2)
- package.json
- assets/mach-token-metadata.json
🚧 Files skipped from review as they are similar to previous changes (1)
- scripts/setup-realms.ts
| HELIUS_API_KEY= # Required for prod; falls back to public RPC | ||
| HELIUS_WEBHOOK_ID= # Set after running scripts/setup-devnet.ts | ||
| HELIUS_WEBHOOK_SECRET= # Random secret for webhook auth header |
There was a problem hiding this comment.
Normalize .env assignments to satisfy dotenv-linter.
The newly added block introduces spacing/format patterns that are currently flagged by dotenv-linter. If linting is enforced, this will keep warning noise (or fail checks).
Proposed cleanup (example)
-HELIUS_API_KEY= # Required for prod; falls back to public RPC
-HELIUS_WEBHOOK_ID= # Set after running scripts/setup-devnet.ts
-HELIUS_WEBHOOK_SECRET= # Random secret for webhook auth header
+HELIUS_API_KEY= # Required for prod; falls back to public RPC
+HELIUS_WEBHOOK_ID= # Set after running scripts/setup-devnet.ts
+HELIUS_WEBHOOK_SECRET= # Random secret for webhook auth header
@@
-SETTLEMENT_TREASURY_PUBKEY= # Receives 15% of all earnings
-SETTLEMENT_DEVELOPER_PUBKEY= # Receives 10% marketplace fee
-SETTLEMENT_VALIDATORS_PUBKEY= # Receives 5% validators pool
+SETTLEMENT_TREASURY_PUBKEY= # Receives 15% of all earnings
+SETTLEMENT_DEVELOPER_PUBKEY= # Receives 10% marketplace fee
+SETTLEMENT_VALIDATORS_PUBKEY= # Receives 5% validators pool
@@
-REALM_ADDRESS= # Maschina DAO realm address
+REALM_ADDRESS= # Maschina DAO realm addressAlso applies to: 56-58, 64-64, 162-166
🧰 Tools
🪛 dotenv-linter (4.0.0)
[warning] 46-46: [SpaceCharacter] The line has spaces around equal sign
(SpaceCharacter)
[warning] 46-46: [ValueWithoutQuotes] This value needs to be surrounded in quotes
(ValueWithoutQuotes)
[warning] 47-47: [SpaceCharacter] The line has spaces around equal sign
(SpaceCharacter)
[warning] 47-47: [ValueWithoutQuotes] This value needs to be surrounded in quotes
(ValueWithoutQuotes)
[warning] 48-48: [SpaceCharacter] The line has spaces around equal sign
(SpaceCharacter)
[warning] 48-48: [ValueWithoutQuotes] This value needs to be surrounded in quotes
(ValueWithoutQuotes)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.env.example around lines 46 - 48, The .env example entries (e.g.,
HELIUS_API_KEY, HELIUS_WEBHOOK_ID, HELIUS_WEBHOOK_SECRET) use extra spacing
around the '=' which triggers dotenv-linter; normalize them to the standard
dotenv assignment format with no spaces around '=', e.g., KEY=VALUE or KEY=
(empty value) and keep any explanatory comments after a single space following
the value; apply the same normalization to the other flagged blocks (lines
around the other HELIUS entries and the ranges noted: 56-58, 64, 162-166).
| const mintKeypairPath = path.join( | ||
| path.dirname(new URL(import.meta.url).pathname), | ||
| "..", | ||
| "wallets", | ||
| "mach-mint.json", | ||
| ); | ||
| fs.writeFileSync(mintKeypairPath, JSON.stringify(Array.from(mintKeypair.secretKey))); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n scripts/setup-mach-token.ts | head -110 | tail -40Repository: RustMunkey/maschina
Length of output: 1725
🏁 Script executed:
head -20 scripts/setup-mach-token.tsRepository: RustMunkey/maschina
Length of output: 637
🌐 Web query:
fileURLToPath vs new URL import.meta.url pathname Node.js best practices
💡 Result:
In Node.js ES modules (ESM), fileURLToPath(import.meta.url) from the 'node:url' module converts the file:// URL provided by import.meta.url into a native filesystem path equivalent to filename from CommonJS. This is necessary because import.meta.url is a URL string (e.g., 'file:///path/to/file.mjs'), while most Node.js APIs like fs and path expect filesystem paths. new URL(import.meta.url).pathname extracts just the path component from the URL but keeps it URL-encoded and uses forward slashes, which can cause issues on Windows (e.g., drive letters like /C:/ become invalid paths). It is not recommended for filesystem operations. Best practices (as of Node.js v25+ in 2026): 1. For Node.js >= v21.2.0 (stable since v24): Use import.meta.filename (equivalent to fileURLToPath(import.meta.url)) and import.meta.dirname directly. These provide decoded, OS-native paths (backslashes on Windows) and are the modern, concise standard. Example: const filename = import.meta.filename; const dirname = import.meta.dirname; 2. For compatibility or older Node.js: Use fileURLToPath + path.dirname. Example: import { fileURLToPath } from 'node:url'; import { dirname } from 'node:path'; const filename = fileURLToPath(import.meta.url); const dirname = dirname(filename); Prefer URL-based resolution for relative files where possible (e.g., new URL('./data.json', import.meta.url)) as it avoids paths entirely and works cross-platform, but convert to paths with fileURLToPath when needed for legacy APIs. Avoid new URL(import.meta.url).pathname due to cross-platform bugs.
Citations:
- 1: https://nodejs.org/api/esm.html
- 2: https://www.thenodebook.com/modules/import-meta-caching
- 3: https://sonarsource.com/blog/dirname-node-js-es-modules
- 4: https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-js-when-using-es6-modules/65895043
- 5: https://electrovir.com/2024-07-23-esm-filename
🏁 Script executed:
# Check if wallets directory exists in the repository
find . -type d -name "wallets" 2>/dev/nullRepository: RustMunkey/maschina
Length of output: 45
Use fileURLToPath for cross-platform path handling and ensure the wallets directory exists before writing.
Using new URL(import.meta.url).pathname has known cross-platform issues—it keeps URL encoding and forward slashes, which breaks on Windows (drive letters become /C:/). Additionally, writing to wallets/ without ensuring the directory exists will fail if the path hasn't been created yet.
Proposed fix
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
+import { fileURLToPath } from "node:url";
@@
- const mintKeypairPath = path.join(
- path.dirname(new URL(import.meta.url).pathname),
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
+ const mintKeypairPath = path.join(
+ scriptDir,
"..",
"wallets",
"mach-mint.json",
);
- fs.writeFileSync(mintKeypairPath, JSON.stringify(Array.from(mintKeypair.secretKey)));
+ fs.mkdirSync(path.dirname(mintKeypairPath), { recursive: true });
+ fs.writeFileSync(
+ mintKeypairPath,
+ JSON.stringify(Array.from(mintKeypair.secretKey)),
+ { mode: 0o600 },
+ );📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const mintKeypairPath = path.join( | |
| path.dirname(new URL(import.meta.url).pathname), | |
| "..", | |
| "wallets", | |
| "mach-mint.json", | |
| ); | |
| fs.writeFileSync(mintKeypairPath, JSON.stringify(Array.from(mintKeypair.secretKey))); | |
| const scriptDir = path.dirname(fileURLToPath(import.meta.url)); | |
| const mintKeypairPath = path.join( | |
| scriptDir, | |
| "..", | |
| "wallets", | |
| "mach-mint.json", | |
| ); | |
| fs.mkdirSync(path.dirname(mintKeypairPath), { recursive: true }); | |
| fs.writeFileSync( | |
| mintKeypairPath, | |
| JSON.stringify(Array.from(mintKeypair.secretKey)), | |
| { mode: 0o600 }, | |
| ); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/setup-mach-token.ts` around lines 94 - 100, Replace the use of new
URL(import.meta.url).pathname with fileURLToPath(import.meta.url) to get a
correct platform path and ensure the target wallets directory exists before
writing: compute mintKeypairPath using fileURLToPath(import.meta.url), call
fs.mkdirSync(path.dirname(mintKeypairPath), { recursive: true }) (or equivalent)
to create the wallets directory if missing, then call
fs.writeFileSync(mintKeypairPath,
JSON.stringify(Array.from(mintKeypair.secretKey))). Ensure references to
mintKeypairPath, import.meta.url, fileURLToPath, fs.mkdirSync, and
fs.writeFileSync are updated accordingly.
| const metadataExtension = TYPE_SIZE + LENGTH_SIZE + pack(metadata).length; | ||
| const mintLen = getMintLen([ExtensionType.MetadataPointer]); | ||
| const mintLamports = await connection.getMinimumBalanceForRentExemption( | ||
| mintLen + metadataExtension, | ||
| ); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n scripts/setup-mach-token.ts | sed -n '110,140p'Repository: RustMunkey/maschina
Length of output: 1366
🏁 Script executed:
rg -n "createAccount" scripts/setup-mach-token.tsRepository: RustMunkey/maschina
Length of output: 100
Allocate full mint account size including metadata extension.
lamports are computed for mintLen + metadataExtension, but createAccount allocates only mintLen. This can break metadata initialization due to insufficient account space.
Proposed fix
SystemProgram.createAccount({
fromPubkey: authority.publicKey,
newAccountPubkey: mintKeypair.publicKey,
- space: mintLen,
+ space: mintLen + metadataExtension,
lamports: mintLamports,
programId: TOKEN_2022_PROGRAM_ID,
}),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const metadataExtension = TYPE_SIZE + LENGTH_SIZE + pack(metadata).length; | |
| const mintLen = getMintLen([ExtensionType.MetadataPointer]); | |
| const mintLamports = await connection.getMinimumBalanceForRentExemption( | |
| mintLen + metadataExtension, | |
| ); | |
| SystemProgram.createAccount({ | |
| fromPubkey: authority.publicKey, | |
| newAccountPubkey: mintKeypair.publicKey, | |
| space: mintLen + metadataExtension, | |
| lamports: mintLamports, | |
| programId: TOKEN_2022_PROGRAM_ID, | |
| }), |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/setup-mach-token.ts` around lines 117 - 121, The code computes
metadataExtension and mintLen but only uses mintLen when allocating the mint
account, causing insufficient space/lamports; create a single fullMintLen =
mintLen + metadataExtension (or reuse metadataExtension when calling
createAccount) and use fullMintLen both when calling
getMinimumBalanceForRentExemption (mintLamports) and when passing the space/size
argument to createAccount so the created account has room for the metadata
extension; update any places that referenced mintLen for allocation to use
fullMintLen (symbols: metadataExtension, mintLen, mintLamports, createAccount).
| const CLUSTER = process.env.SOLANA_CLUSTER ?? "devnet"; | ||
|
|
||
| const ASSETS_DIR = path.join(path.dirname(new URL(import.meta.url).pathname), "..", "assets"); | ||
| const IMAGE_PATH = path.join(ASSETS_DIR, "mach-token.png"); | ||
| const METADATA_PATH = path.join(ASSETS_DIR, "mach-token-metadata.json"); | ||
|
|
||
| function getIrysUrl(): string { | ||
| return CLUSTER === "mainnet-beta" ? "https://node1.irys.xyz" : "https://devnet.irys.xyz"; | ||
| } |
There was a problem hiding this comment.
Validate SOLANA_CLUSTER and avoid hardcoded devnet follow-up instructions.
Unknown cluster values currently fall through network selection logic, and the summary command always prints SOLANA_CLUSTER=devnet.
Proposed fix
-const CLUSTER = process.env.SOLANA_CLUSTER ?? "devnet";
+function parseCluster(): "devnet" | "mainnet-beta" {
+ const value = (process.env.SOLANA_CLUSTER ?? "devnet").trim();
+ if (value !== "devnet" && value !== "mainnet-beta") {
+ throw new Error(`Invalid SOLANA_CLUSTER: ${value}`);
+ }
+ return value;
+}
+
+const CLUSTER = parseCluster();
@@
- console.log(" 2. Run: SOLANA_CLUSTER=devnet npx tsx scripts/setup-mach-token.ts");
+ console.log(` 2. Run: SOLANA_CLUSTER=${CLUSTER} npx tsx scripts/setup-mach-token.ts`);Also applies to: 36-50, 131-131
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/upload-arweave.ts` around lines 26 - 34, The code allows invalid
SOLANA_CLUSTER values and prints a hardcoded "devnet" in summary; validate and
normalize CLUSTER by checking process.env.SOLANA_CLUSTER against allowed values
("mainnet-beta", "devnet", "localnet" or your supported set) in the CLUSTER
constant initialization (or throw/exit on unknown values), update getIrysUrl()
to explicitly handle only allowed clusters and throw or error for unsupported
ones, and change any summary/print logic that currently hardcodes "devnet" to
use the CLUSTER variable so the printed command reflects the actual selected
cluster (refer to CLUSTER, getIrysUrl, and the summary print site).
| const ASSETS_DIR = path.join(path.dirname(new URL(import.meta.url).pathname), "..", "assets"); | ||
| const IMAGE_PATH = path.join(ASSETS_DIR, "mach-token.png"); | ||
| const METADATA_PATH = path.join(ASSETS_DIR, "mach-token-metadata.json"); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "upload-arweave.ts" | head -20Repository: RustMunkey/maschina
Length of output: 90
🏁 Script executed:
cat -n scripts/upload-arweave.ts | head -40Repository: RustMunkey/maschina
Length of output: 1661
🌐 Web query:
fileURLToPath vs new URL import.meta.url pathname Windows path resolution
💡 Result:
In Node.js ES modules, fileURLToPath(import.meta.url) and new URL(import.meta.url).pathname both convert the file:// URL of the current module to a filesystem path, but they differ significantly on Windows for correct path resolution. import.meta.url provides a file:// URL (e.g., 'file:///C:/path/to/file.js' on Windows). - new URL(import.meta.url).pathname returns a URL-standard pathname starting with a leading slash: '/C:/path/to/file.js'. This is invalid for Windows fs APIs like path.join or fs.readFileSync, causing errors like double drive letters (e.g., 'C:\C:\path') because path.join treats the leading '/' specially on Windows. - url.fileURLToPath(import.meta.url) returns the correct platform-native path: 'C:\path\to\file.js' (with backslashes on Windows, no leading slash before drive letter, percent-decoding applied). This works properly with fs and path modules cross-platform. Official Node.js docs explicitly warn: new URL('file:///C:/path/').pathname is INCORRECT (/C:/path/), while fileURLToPath('file:///C:/path/') is CORRECT (C:\path\ on Windows). Use fileURLToPath for reliable path resolution, especially for emulating dirname/filename in ESM or relative file access. Modern Node (v21+) also offers import.meta.dirname directly. Example: import { fileURLToPath } from 'node:url'; import { dirname } from 'node:path'; const filename = fileURLToPath(import.meta.url); const dirname = dirname(filename); // C:\path\to on Windows
Citations:
- 1: new URL('.', import.meta.url).pathname produces erroneous path on Windows 10 nodejs/node#37845
- 2: https://2ality.com/2022/07/nodejs-path.html
- 3: fileURLToPath returns forward slashes on Windows nodejs/node#25265
- 4: https://cirrus.twiddles.com/blog/2024/08/20/fixing-node-js-paths-on-windows/
- 5: https://nodejs.org/api/url.html
- 6: https://nodejs.org/download/release/v22.12.0/docs/api/url.html
Use fileURLToPath for robust ESM path resolution.
new URL(import.meta.url).pathname breaks on Windows systems. The .pathname property returns a URL-standard path with a leading slash (e.g., /C:/path/to/file.js), which causes path resolution errors and encoded path segment mishandling. fileURLToPath correctly returns the platform-native path and is the official Node.js recommendation.
Proposed fix
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
+import { fileURLToPath } from "node:url";
@@
-const ASSETS_DIR = path.join(path.dirname(new URL(import.meta.url).pathname), "..", "assets");
+const ASSETS_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "assets");📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const ASSETS_DIR = path.join(path.dirname(new URL(import.meta.url).pathname), "..", "assets"); | |
| const IMAGE_PATH = path.join(ASSETS_DIR, "mach-token.png"); | |
| const METADATA_PATH = path.join(ASSETS_DIR, "mach-token-metadata.json"); | |
| import fs from "node:fs"; | |
| import os from "node:os"; | |
| import path from "node:path"; | |
| import { fileURLToPath } from "node:url"; | |
| const ASSETS_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "assets"); | |
| const IMAGE_PATH = path.join(ASSETS_DIR, "mach-token.png"); | |
| const METADATA_PATH = path.join(ASSETS_DIR, "mach-token-metadata.json"); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/upload-arweave.ts` around lines 28 - 30, The current ESM path
resolution using path.dirname(new URL(import.meta.url).pathname) is fragile on
Windows; import fileURLToPath from 'url' and replace that expression with
path.dirname(fileURLToPath(import.meta.url)) so ASSETS_DIR, IMAGE_PATH, and
METADATA_PATH are computed using a platform-native path; also add the import {
fileURLToPath } from 'url' at the top of the module so the new call resolves
correctly.
| function loadKeypairPath(): string { | ||
| return process.env.ANCHOR_WALLET ?? path.join(os.homedir(), ".config", "solana", "id.json"); | ||
| } |
There was a problem hiding this comment.
Support SOLANA_WALLET_PATH and expand ~/ for keypair loading.
The loader still only checks ANCHOR_WALLET; this is inconsistent with .env.example and breaks common ~/... paths in Node.
Proposed fix
function loadKeypairPath(): string {
- return process.env.ANCHOR_WALLET ?? path.join(os.homedir(), ".config", "solana", "id.json");
+ const configured =
+ process.env.SOLANA_WALLET_PATH ??
+ process.env.ANCHOR_WALLET ??
+ path.join(os.homedir(), ".config", "solana", "id.json");
+ return configured.startsWith("~/")
+ ? path.join(os.homedir(), configured.slice(2))
+ : configured;
}Also applies to: 69-71
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/upload-arweave.ts` around lines 52 - 54, Update the keypair loader to
honor SOLANA_WALLET_PATH (fallback to ANCHOR_WALLET) and expand leading "~/" to
the user home directory; specifically, in loadKeypairPath (and the similar
loader around lines 69-71) check process.env.SOLANA_WALLET_PATH first, then
process.env.ANCHOR_WALLET, and if the chosen path starts with "~/" replace that
prefix with os.homedir() (using path.join to construct the remainder) so both
env names and tilde-prefixed paths are handled correctly.
| const metadataRaw = fs.readFileSync(METADATA_PATH, "utf-8"); | ||
| const metadata = JSON.parse(metadataRaw); | ||
| metadata.image = imageUri; | ||
| metadata.properties.files[0].uri = imageUri; | ||
| const metadataPatched = JSON.stringify(metadata, null, 2); |
There was a problem hiding this comment.
Guard metadata shape before mutating nested fields.
This block assumes metadata.properties.files[0] exists. A malformed JSON file will throw at runtime without a clear error.
Proposed fix
- const metadata = JSON.parse(metadataRaw);
+ const metadata = JSON.parse(metadataRaw) as {
+ image?: string;
+ properties?: { files?: Array<{ uri?: string }> };
+ };
+ if (!metadata.properties?.files?.[0]) {
+ throw new Error("Invalid metadata schema: expected properties.files[0]");
+ }
metadata.image = imageUri;
metadata.properties.files[0].uri = imageUri;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/upload-arweave.ts` around lines 105 - 109, The code assumes
metadata.properties.files[0] exists before mutating it; add a guard that
validates the parsed metadata shape after JSON.parse(metadataRaw) — ensure
metadata is an object, metadata.properties is an object,
metadata.properties.files is an array and files[0] exists (or initialize
properties/files to sensible defaults) before setting metadata.image and
metadata.properties.files[0].uri; if validation fails, throw a clear error that
includes METADATA_PATH and the missing field so callers know the JSON is
malformed.
ae407bd to
466ae2f
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (10)
scripts/upload-arweave.ts (4)
28-30:⚠️ Potential issue | 🟠 MajorUse
fileURLToPathfor cross-platform ESM path resolution.Line 28 uses
new URL(import.meta.url).pathname, which is not robust on Windows path semantics.Proposed fix
import fs from "node:fs"; import os from "node:os"; import path from "node:path"; +import { fileURLToPath } from "node:url"; @@ -const ASSETS_DIR = path.join(path.dirname(new URL(import.meta.url).pathname), "..", "assets"); +const ASSETS_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "assets");#!/bin/bash # Verify current path-conversion usage in this script. rg -n 'new URL\(import\.meta\.url\)\.pathname|fileURLToPath' scripts/upload-arweave.ts🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/upload-arweave.ts` around lines 28 - 30, Replace the brittle new URL(import.meta.url).pathname usage with Node's fileURLToPath conversion: compute ASSETS_DIR by converting import.meta.url to a file path via fileURLToPath(import.meta.url) and then use path.join on that directory to build ASSETS_DIR, leaving IMAGE_PATH and METADATA_PATH as they are; update the top imports to include fileURLToPath from 'url' if not already present and adjust ASSETS_DIR, IMAGE_PATH, and METADATA_PATH definitions to reference the converted file path.
105-109:⚠️ Potential issue | 🟠 MajorGuard metadata shape before mutating nested fields.
Line 108 assumes
metadata.properties.files[0]exists; malformed metadata JSON will throw with an unclear error.Proposed fix
- const metadata = JSON.parse(metadataRaw); + const metadata = JSON.parse(metadataRaw) as { + image?: string; + properties?: { files?: Array<{ uri?: string }> }; + }; + if (!metadata.properties?.files?.[0]) { + throw new Error(`Invalid metadata schema in ${METADATA_PATH}: expected properties.files[0]`); + } metadata.image = imageUri; metadata.properties.files[0].uri = imageUri;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/upload-arweave.ts` around lines 105 - 109, The code assumes metadata.properties.files[0] exists before mutating it; update the logic after parsing metadata (metadataRaw → metadata) to validate and guard the shape: check that metadata is an object, metadata.properties exists, metadata.properties.files is an array and has at least one entry (or create the nested structure if missing), and only then set metadata.image and metadata.properties.files[0].uri; if the shape is invalid, throw or log a clear error mentioning the metadata path and include the raw content (or write a fallback files array) before serializing to metadataPatched to prevent unclear runtime exceptions.
26-34:⚠️ Potential issue | 🟠 MajorValidate
SOLANA_CLUSTERand avoid hardcoded devnet follow-up output.Line 26-34 accepts unsupported cluster values and silently falls back to devnet behavior; Line 131 always prints
devneteven when running mainnet-beta.Proposed fix
-const CLUSTER = process.env.SOLANA_CLUSTER ?? "devnet"; +function parseCluster(): "devnet" | "mainnet-beta" { + const value = (process.env.SOLANA_CLUSTER ?? "devnet").trim(); + if (value !== "devnet" && value !== "mainnet-beta") { + throw new Error(`Invalid SOLANA_CLUSTER: ${value}. Must be "devnet" or "mainnet-beta".`); + } + return value; +} +const CLUSTER = parseCluster(); @@ -function getIrysUrl(): string { - return CLUSTER === "mainnet-beta" ? "https://node1.irys.xyz" : "https://devnet.irys.xyz"; -} +function getIrysUrl(): string { + if (CLUSTER === "mainnet-beta") return "https://node1.irys.xyz"; + if (CLUSTER === "devnet") return "https://devnet.irys.xyz"; + throw new Error(`Unsupported SOLANA_CLUSTER: ${CLUSTER}`); +} @@ - console.log(" 2. Run: SOLANA_CLUSTER=devnet npx tsx scripts/setup-mach-token.ts"); + console.log(` 2. Run: SOLANA_CLUSTER=${CLUSTER} npx tsx scripts/setup-mach-token.ts`);Also applies to: 131-131
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/upload-arweave.ts` around lines 26 - 34, Validate the SOLANA_CLUSTER value before using it: replace the current CLUSTER assignment with a check against an allowed set (e.g., "devnet", "mainnet-beta", "testnet") and if the env value is invalid log a warning and fall back to "devnet"; update any hardcoded prints that show the cluster (the status/logging statement that currently prints "devnet" at the end of the script) to use the CLUSTER variable so output reflects the actual validated value; ensure getIrysUrl() continues to derive its URL from the validated CLUSTER constant.
52-54:⚠️ Potential issue | 🟠 MajorHonor
SOLANA_WALLET_PATHand expand~/in wallet path loading.Line 52-54 only supports
ANCHOR_WALLET, which is inconsistent with.env.exampleand fails for common tilde-prefixed paths.Proposed fix
function loadKeypairPath(): string { - return process.env.ANCHOR_WALLET ?? path.join(os.homedir(), ".config", "solana", "id.json"); + const configured = + process.env.SOLANA_WALLET_PATH ?? + process.env.ANCHOR_WALLET ?? + path.join(os.homedir(), ".config", "solana", "id.json"); + return configured.startsWith("~/") + ? path.join(os.homedir(), configured.slice(2)) + : configured; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/upload-arweave.ts` around lines 52 - 54, Update loadKeypairPath to first read SOLANA_WALLET_PATH (falling back to ANCHOR_WALLET) and expand a leading "~/" to the user's home directory; specifically, in the loadKeypairPath function replace the single env var lookup with logic like: prefer process.env.SOLANA_WALLET_PATH || process.env.ANCHOR_WALLET, and if the chosen path starts with "~/" replace that prefix with os.homedir() (then normalize/resolve with path.join/path.resolve) so tilde-prefixed paths are correctly expanded.scripts/setup-devnet.ts (2)
11-13:⚠️ Potential issue | 🟡 MinorHeader comment overstates runtime behavior.
Line 11 says the script verifies deployment at
SETTLEMENT_PROGRAM_ID, but no RPC/program-account check is performed.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/setup-devnet.ts` around lines 11 - 13, The header comment incorrectly claims the script verifies the settlement program at SETTLEMENT_PROGRAM_ID but no such check exists; either remove/soften that line or implement a runtime verification: create or use the existing RPC Connection and call connection.getAccountInfo(new PublicKey(SETTLEMENT_PROGRAM_ID)) (or connection.getProgramAccounts) and fail/log if the account/program is not found. Update the comment if you choose to remove the claim, or add the getAccountInfo check and a clear error message referencing SETTLEMENT_PROGRAM_ID if you choose to implement verification.
41-41:⚠️ Potential issue | 🟠 MajorNormalize
API_BASE_URLbefore buildingwebhookUrl.Line 41 can produce double slashes when
API_BASE_URLends with/, which can break webhook deduplication inregisterSettlementWebhook.Proposed fix
- const apiBaseUrl = process.env.API_BASE_URL; + const apiBaseUrl = process.env.API_BASE_URL?.replace(/\/+$/, ""); if (!apiBaseUrl) { @@ - const webhookUrl = `${apiBaseUrl}/webhooks/helius`; + const webhookUrl = new URL("/webhooks/helius", `${apiBaseUrl}/`).toString();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/setup-devnet.ts` at line 41, The webhookUrl concatenation can produce double slashes when API_BASE_URL (apiBaseUrl) ends with a trailing slash; update the code that builds webhookUrl (const webhookUrl = `${apiBaseUrl}/webhooks/helius`;) to normalize apiBaseUrl first — e.g., remove any trailing slash from API_BASE_URL or use a URL-joining approach — so that webhookUrl is always a single-slash path; ensure registerSettlementWebhook receives the normalized webhookUrl..env.example (1)
46-48:⚠️ Potential issue | 🟡 MinorNormalize
.envassignments to remove persistent linter noise.Line 46-48, Line 56-58, and Line 64 still use padded assignment formatting (
KEY= # ...), which keepsdotenv-linterwarnings alive.Proposed cleanup
-HELIUS_API_KEY= # Required for prod; falls back to public RPC -HELIUS_WEBHOOK_ID= # Set after running scripts/setup-devnet.ts -HELIUS_WEBHOOK_SECRET= # Random secret for webhook auth header +HELIUS_API_KEY= # Required for prod; falls back to public RPC +HELIUS_WEBHOOK_ID= # Set after running scripts/setup-devnet.ts +HELIUS_WEBHOOK_SECRET= # Random secret for webhook auth header @@ -SETTLEMENT_TREASURY_PUBKEY= # Receives 15% of all earnings -SETTLEMENT_DEVELOPER_PUBKEY= # Receives 10% marketplace fee -SETTLEMENT_VALIDATORS_PUBKEY= # Receives 5% validators pool +SETTLEMENT_TREASURY_PUBKEY= # Receives 15% of all earnings +SETTLEMENT_DEVELOPER_PUBKEY= # Receives 10% marketplace fee +SETTLEMENT_VALIDATORS_PUBKEY= # Receives 5% validators pool @@ -REALM_ADDRESS= # Maschina DAO realm address +REALM_ADDRESS= # Maschina DAO realm addressAlso applies to: 56-58, 64-64
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.env.example around lines 46 - 48, Normalize the .env assignments by removing the padded spaces after the equals sign so the comment follows immediately (single space) — e.g., change HELIUS_API_KEY= # ... to HELIUS_API_KEY= # ...; do the same for HELIUS_WEBHOOK_ID and HELIUS_WEBHOOK_SECRET and apply the same cleanup pattern to the other occurrences mentioned (the blocks around lines 56-58 and 64) to silence dotenv-linter warnings.programs/settlement/src/instructions/add_earnings.rs (2)
16-19:⚠️ Potential issue | 🔴 CriticalMake
run_ididempotent on-chain.
run_idonly reaches the event today, so a retry or replay transfers tokens and increments the pool again. ReuseExecutionReceiptor add a dedicated PDA keyed byrun_id, and reject duplicates before the CPI transfer.Also applies to: 73-124
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@programs/settlement/src/instructions/add_earnings.rs` around lines 16 - 19, Add on-chain idempotency for AddEarningsArgs.run_id by storing and checking a PDA or reusing ExecutionReceipt before performing the CPI token transfer: when handling the AddEarnings instruction, derive a PDA keyed by run_id (or load the existing ExecutionReceipt tied to run_id), check if it already exists and abort with an error if so, and only proceed to the CPI transfer and increment the pool if the PDA/receipt is absent; ensure the PDA is created/marked as consumed atomically within the same transaction that does the transfer to prevent races.
35-68:⚠️ Potential issue | 🔴 CriticalGate
add_earningsbehind the configured settlement authority.Right now any signer with a matching token account can transfer funds into the vault and mutate
pool.pending_*/run_count. Mirror theconfig.authorityconstraint already used inprograms/settlement/src/instructions/settle_earnings.rsso only the trusted settlement authority can record earnings.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@programs/settlement/src/instructions/add_earnings.rs` around lines 35 - 68, The add_earnings handler currently accepts any Signer that controls the matching token account; mirror the settle_earnings authority check by adding the program Config account to the context (e.g., pub config: Account<'info, Config>) and restrict the authority signer to the configured settlement authority (change the authority account annotation to require address = config.authority or equivalent Anchor constraint used in settle_earnings.rs). Update the account list for add_earnings to include config and replace the unrestricted #[account(mut)] pub authority: Signer<'info> with the constrained form that enforces config.authority so only the trusted settlement authority can mutate pool.pending_* and run_count.programs/settlement/src/instructions/init_node_vault.rs (1)
29-43:⚠️ Potential issue | 🔴 CriticalStill missing canonical mint enforcement.
usdc_mintis caller-controlled on the one-time vault init, so the first initializer can bind the vault PDA to the wrong mint and break every later USDC settlement flow for that node. Pin this to a canonical mint constant or a mint stored inSettlementConfigbefore theinitruns.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@programs/settlement/src/instructions/init_node_vault.rs` around lines 29 - 43, The usdc_mint Account<'info, Mint> passed to the init for the per-node vault (the account tied to VAULT_SEED and args.node_id in init_node_vault.rs) must be canonicalized — don't allow the initializer to pick any mint. Fix by pinning the mint to a known source: either replace the caller-supplied usdc_mint with the canonical mint stored on the program (e.g., SettlementConfig.usdc_mint) and change the account constraint/token::mint to reference that stored mint, or validate the provided usdc_mint against a constant canonical pubkey (require_keys_eq or an explicit check) before performing the init of the vault; update the account definition and the init precondition logic (referencing usdc_mint, VAULT_SEED, args.node_id, and SettlementConfig) so the vault is always created with the canonical mint.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.env.example:
- Line 176: There is a duplicate APP_URL environment key; remove the redundant
APP_URL entry so only one APP_URL variable remains (keep the canonical
definition already present and delete the later redefinition) to prevent dotenv
from overriding the intended public URL for magic links and other features.
In `@programs/settlement/src/instructions/initialize_config.rs`:
- Around line 22-34: The initialize_config flow currently allows any signer to
init the singleton config PDA and set themselves as authority; restrict this by
enforcing the caller equals a known governance/deployment key before allowing
the init. Add a constraint on the `authority` signer in the `initialize_config`
context (or validate inside the `initialize_config` handler) that checks
`authority.key()` against the expected governance public key (or a governance
PDA) and fail with an unauthorized error if it does not match; apply the same
check for the other identical init block referenced (lines 41-47). Reference the
`authority` Signer, the `config:
Account<SettlementConfig>`/`SettlementConfig::SEED`, and the `initialize_config`
entrypoint when implementing the change.
---
Duplicate comments:
In @.env.example:
- Around line 46-48: Normalize the .env assignments by removing the padded
spaces after the equals sign so the comment follows immediately (single space) —
e.g., change HELIUS_API_KEY= # ... to
HELIUS_API_KEY= # ...; do the same for HELIUS_WEBHOOK_ID and
HELIUS_WEBHOOK_SECRET and apply the same cleanup pattern to the other
occurrences mentioned (the blocks around lines 56-58 and 64) to silence
dotenv-linter warnings.
In `@programs/settlement/src/instructions/add_earnings.rs`:
- Around line 16-19: Add on-chain idempotency for AddEarningsArgs.run_id by
storing and checking a PDA or reusing ExecutionReceipt before performing the CPI
token transfer: when handling the AddEarnings instruction, derive a PDA keyed by
run_id (or load the existing ExecutionReceipt tied to run_id), check if it
already exists and abort with an error if so, and only proceed to the CPI
transfer and increment the pool if the PDA/receipt is absent; ensure the PDA is
created/marked as consumed atomically within the same transaction that does the
transfer to prevent races.
- Around line 35-68: The add_earnings handler currently accepts any Signer that
controls the matching token account; mirror the settle_earnings authority check
by adding the program Config account to the context (e.g., pub config:
Account<'info, Config>) and restrict the authority signer to the configured
settlement authority (change the authority account annotation to require address
= config.authority or equivalent Anchor constraint used in settle_earnings.rs).
Update the account list for add_earnings to include config and replace the
unrestricted #[account(mut)] pub authority: Signer<'info> with the constrained
form that enforces config.authority so only the trusted settlement authority can
mutate pool.pending_* and run_count.
In `@programs/settlement/src/instructions/init_node_vault.rs`:
- Around line 29-43: The usdc_mint Account<'info, Mint> passed to the init for
the per-node vault (the account tied to VAULT_SEED and args.node_id in
init_node_vault.rs) must be canonicalized — don't allow the initializer to pick
any mint. Fix by pinning the mint to a known source: either replace the
caller-supplied usdc_mint with the canonical mint stored on the program (e.g.,
SettlementConfig.usdc_mint) and change the account constraint/token::mint to
reference that stored mint, or validate the provided usdc_mint against a
constant canonical pubkey (require_keys_eq or an explicit check) before
performing the init of the vault; update the account definition and the init
precondition logic (referencing usdc_mint, VAULT_SEED, args.node_id, and
SettlementConfig) so the vault is always created with the canonical mint.
In `@scripts/setup-devnet.ts`:
- Around line 11-13: The header comment incorrectly claims the script verifies
the settlement program at SETTLEMENT_PROGRAM_ID but no such check exists; either
remove/soften that line or implement a runtime verification: create or use the
existing RPC Connection and call connection.getAccountInfo(new
PublicKey(SETTLEMENT_PROGRAM_ID)) (or connection.getProgramAccounts) and
fail/log if the account/program is not found. Update the comment if you choose
to remove the claim, or add the getAccountInfo check and a clear error message
referencing SETTLEMENT_PROGRAM_ID if you choose to implement verification.
- Line 41: The webhookUrl concatenation can produce double slashes when
API_BASE_URL (apiBaseUrl) ends with a trailing slash; update the code that
builds webhookUrl (const webhookUrl = `${apiBaseUrl}/webhooks/helius`;) to
normalize apiBaseUrl first — e.g., remove any trailing slash from API_BASE_URL
or use a URL-joining approach — so that webhookUrl is always a single-slash
path; ensure registerSettlementWebhook receives the normalized webhookUrl.
In `@scripts/upload-arweave.ts`:
- Around line 28-30: Replace the brittle new URL(import.meta.url).pathname usage
with Node's fileURLToPath conversion: compute ASSETS_DIR by converting
import.meta.url to a file path via fileURLToPath(import.meta.url) and then use
path.join on that directory to build ASSETS_DIR, leaving IMAGE_PATH and
METADATA_PATH as they are; update the top imports to include fileURLToPath from
'url' if not already present and adjust ASSETS_DIR, IMAGE_PATH, and
METADATA_PATH definitions to reference the converted file path.
- Around line 105-109: The code assumes metadata.properties.files[0] exists
before mutating it; update the logic after parsing metadata (metadataRaw →
metadata) to validate and guard the shape: check that metadata is an object,
metadata.properties exists, metadata.properties.files is an array and has at
least one entry (or create the nested structure if missing), and only then set
metadata.image and metadata.properties.files[0].uri; if the shape is invalid,
throw or log a clear error mentioning the metadata path and include the raw
content (or write a fallback files array) before serializing to metadataPatched
to prevent unclear runtime exceptions.
- Around line 26-34: Validate the SOLANA_CLUSTER value before using it: replace
the current CLUSTER assignment with a check against an allowed set (e.g.,
"devnet", "mainnet-beta", "testnet") and if the env value is invalid log a
warning and fall back to "devnet"; update any hardcoded prints that show the
cluster (the status/logging statement that currently prints "devnet" at the end
of the script) to use the CLUSTER variable so output reflects the actual
validated value; ensure getIrysUrl() continues to derive its URL from the
validated CLUSTER constant.
- Around line 52-54: Update loadKeypairPath to first read SOLANA_WALLET_PATH
(falling back to ANCHOR_WALLET) and expand a leading "~/" to the user's home
directory; specifically, in the loadKeypairPath function replace the single env
var lookup with logic like: prefer process.env.SOLANA_WALLET_PATH ||
process.env.ANCHOR_WALLET, and if the chosen path starts with "~/" replace that
prefix with os.homedir() (then normalize/resolve with path.join/path.resolve) so
tilde-prefixed paths are correctly expanded.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5e85c595-ddb9-4268-bb3c-f4df4365f4cf
⛔ Files ignored due to path filters (3)
assets/mach-token.pngis excluded by!**/*.pngpnpm-lock.yamlis excluded by!**/pnpm-lock.yamlprograms/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (35)
.env.example.gitignoreapps/docs/src/content/introduction.mdxapps/docs/src/routes/introduction.tsxapps/web/src/routes/about.tsxapps/web/src/routes/careers.tsxapps/web/src/routes/index.tsxapps/web/src/routes/marketplace.tsxapps/web/src/routes/press-kit.tsxapps/web/src/routes/security.tsxapps/web/src/routes/terms.tsxascii-logo.txtassets/mach-token-metadata.jsonpackage.jsonpackages/chain/package.jsonpackages/marketplace/src/marketplace.test.tsprograms/Anchor.tomlprograms/Cargo.tomlprograms/settlement/Cargo.tomlprograms/settlement/src/errors.rsprograms/settlement/src/instructions/add_earnings.rsprograms/settlement/src/instructions/init_node_vault.rsprograms/settlement/src/instructions/initialize_config.rsprograms/settlement/src/instructions/mod.rsprograms/settlement/src/instructions/register_node.rsprograms/settlement/src/instructions/settle_earnings.rsprograms/settlement/src/lib.rsprograms/settlement/src/state.rsprograms/settlement/target/idl/settlement.jsonscripts/discord-scaffold.mjsscripts/setup-devnet.tsscripts/setup-mach-token.tsscripts/setup-realms.tsscripts/test-settlement-e2e.tsscripts/upload-arweave.ts
✅ Files skipped from review due to trivial changes (19)
- apps/docs/src/routes/introduction.tsx
- apps/web/src/routes/careers.tsx
- .gitignore
- apps/docs/src/content/introduction.mdx
- apps/web/src/routes/security.tsx
- apps/web/src/routes/about.tsx
- ascii-logo.txt
- apps/web/src/routes/index.tsx
- programs/Cargo.toml
- apps/web/src/routes/terms.tsx
- apps/web/src/routes/press-kit.tsx
- packages/marketplace/src/marketplace.test.ts
- package.json
- packages/chain/package.json
- assets/mach-token-metadata.json
- programs/settlement/Cargo.toml
- programs/settlement/src/instructions/mod.rs
- apps/web/src/routes/marketplace.tsx
- programs/settlement/target/idl/settlement.json
🚧 Files skipped from review as they are similar to previous changes (7)
- scripts/discord-scaffold.mjs
- programs/Anchor.toml
- scripts/setup-realms.ts
- programs/settlement/src/instructions/register_node.rs
- scripts/setup-mach-token.ts
- scripts/test-settlement-e2e.ts
- programs/settlement/src/lib.rs
| NODE_ENV=development | ||
|
|
||
| # ─── App ───────────────────────────────────────────────────────────────────── | ||
| APP_URL=http://localhost:3000 # public-facing URL for magic link emails etc. |
There was a problem hiding this comment.
Remove the duplicate APP_URL key to avoid config override bugs.
Line 176 redefines APP_URL (already defined at Line 143) with a different value. In dotenv, the last assignment wins, so app links can silently point to the wrong host.
Proposed fix
-APP_URL=http://localhost:3000 # public-facing URL for magic link emails etc.
+# APP_URL is already defined above (Line 143). Avoid redefining it here.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| APP_URL=http://localhost:3000 # public-facing URL for magic link emails etc. | |
| # APP_URL is already defined above (Line 143). Avoid redefining it here. |
🧰 Tools
🪛 dotenv-linter (4.0.0)
[warning] 176-176: [DuplicatedKey] The APP_URL key is duplicated
(DuplicatedKey)
[warning] 176-176: [ValueWithoutQuotes] This value needs to be surrounded in quotes
(ValueWithoutQuotes)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.env.example at line 176, There is a duplicate APP_URL environment key;
remove the redundant APP_URL entry so only one APP_URL variable remains (keep
the canonical definition already present and delete the later redefinition) to
prevent dotenv from overriding the intended public URL for magic links and other
features.
| /// Maschina settlement authority — becomes the only key allowed to settle. | ||
| #[account(mut)] | ||
| pub authority: Signer<'info>, | ||
|
|
||
| /// Global config PDA — created once, never re-initialised. | ||
| #[account( | ||
| init, | ||
| payer = authority, | ||
| space = SettlementConfig::LEN, | ||
| seeds = [SettlementConfig::SEED], | ||
| bump, | ||
| )] | ||
| pub config: Account<'info, SettlementConfig>, |
There was a problem hiding this comment.
initialize_config is currently first-caller-wins.
Any signer can create the singleton config PDA and set themselves as config.authority, so the first external caller permanently controls settlement authorization and the developer/treasury/validators payout keys. This needs to be gated to a known governance/deployment authority before the init executes.
Also applies to: 41-47
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@programs/settlement/src/instructions/initialize_config.rs` around lines 22 -
34, The initialize_config flow currently allows any signer to init the singleton
config PDA and set themselves as authority; restrict this by enforcing the
caller equals a known governance/deployment key before allowing the init. Add a
constraint on the `authority` signer in the `initialize_config` context (or
validate inside the `initialize_config` handler) that checks `authority.key()`
against the expected governance public key (or a governance PDA) and fail with
an unauthorized error if it does not match; apply the same check for the other
identical init block referenced (lines 41-47). Reference the `authority` Signer,
the `config: Account<SettlementConfig>`/`SettlementConfig::SEED`, and the
`initialize_config` entrypoint when implementing the change.
Summary by CodeRabbit
New Features
Bug Fixes
Tests
Documentation
Configuration